
package de.ivu.elect.was.exchange.wahl;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collectors;

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

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import de.ivu.elect.business.gebietsbaum.boundary.GebietFactory;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gebietsbaum.entity.GebietTK;
import de.ivu.elect.business.gebietsbaum.entity.Gebietsart;
import de.ivu.elect.business.gebietsbaum.entity.NlGebietCustomData;
import de.ivu.elect.business.gebietsbaum.entity.NlGebietsart;
import de.ivu.elect.business.gruppe.boundary.PolitischeGruppeFactory;
import de.ivu.elect.business.gruppe.entity.Gruppe;
import de.ivu.elect.business.gruppe.entity.NlPolitischeGruppe;
import de.ivu.elect.business.gruppe.entity.PolitischeGruppe;
import de.ivu.elect.business.mandant.entity.Mandant;
import de.ivu.elect.business.wahl.boundary.WahlFactory;
import de.ivu.elect.business.wahl.boundary.WahlterminFactory;
import de.ivu.elect.business.wahl.control.Wahldaten;
import de.ivu.elect.business.wahl.entity.NlWahlCustomData;
import de.ivu.elect.business.wahl.entity.NlWahlart;
import de.ivu.elect.business.wahl.entity.Wahl;
import de.ivu.elect.business.wahl.entity.WahlParameter;
import de.ivu.elect.business.wahl.entity.WahlParameterWerte;
import de.ivu.elect.business.wahl.entity.Wahlart;
import de.ivu.elect.nl.eml.EmlWrapper;
import de.ivu.elect.nl.eml.XmlHelper;
import de.ivu.elect.nl.eml.readable.Eml110aWrapper;
import de.ivu.elect.presentation.NlSystemProperties;
import de.ivu.elect.presentation.gebietsbaum.Roman;
import de.ivu.wahl.wus.oasis.eml.ElectionIdentifierStructure110A;
import de.ivu.wahl.wus.oasis.eml.kiesraad.Committee;
import de.ivu.wahl.wus.oasis.eml.kiesraad.CommitteeCategoryType;
import de.ivu.wahl.wus.oasis.eml.kiesraad.ElectionDomain;
import de.ivu.wahl.wus.oasis.eml.kiesraad.ElectionSubcategoryType;
import de.ivu.wahl.wus.oasis.eml.kiesraad.ObjectFactory;
import de.ivu.wahl.wus.oasis.eml.kiesraad.Region;
import de.ivu.wahl.wus.oasis.eml.kiesraad.RegionCategoryType;
import de.ivu.wahl.wus.oasis.eml.kiesraad.RegisteredParty;


import static de.ivu.elect.business.wahl.entity.WahlParameterWerte.DOKUMENT_PLATZHALTER_BUNDESLAND_NAME;
import static de.ivu.elect.business.wahl.entity.WahlParameterWerte.DOKUMENT_PLATZHALTER_GEMEINDE_NAME;
import static de.ivu.elect.business.wahl.entity.WahlParameterWerte.DOKUMENT_PLATZHALTER_GEMEINDE_NUMMER;
import static de.ivu.elect.business.wahl.entity.WahlParameterWerte.DOKUMENT_PLATZHALTER_WAHLBEHOERRDE_NAME;
import static de.ivu.elect.business.wahl.entity.WahlParameterWerte.DOKUMENT_PLATZHALTER_WAHLKREIS_NAME;
import static de.ivu.elect.business.wahl.entity.WahlParameterWerte.DOKUMENT_PLATZHALTER_WAHLKREIS_NUMMER;

public class Eml110Mapper {

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

  @Inject
  private transient WahlFactory wahlFactory;

  @Inject
  private transient WahlterminFactory wahlterminFactory;

  @Inject
  private transient NlWahlartEmlMapper nlWahlartEmlMapper;

  @Inject
  private transient GebietFactory gebietFactory;

  @Inject
  private transient NlGebietsartEmlMapper nlGebietsartEmlMapper;

  @Inject
  private transient PolitischeGruppeFactory politischeGruppeFactory;

  @Inject
  private transient NlSystemProperties nlSystemProperties;

  @Inject
  private transient EmlCommonMapper emlCommonMapper;

  public Wahl mappeWahl(EmlWrapper emlWrapper) {
    Wahl wahl = wahlFactory.create();
    Eml110aWrapper eml110aWrapper = new Eml110aWrapper(emlWrapper.getContent());
    ElectionIdentifierStructure110A electionIdentifier110a = eml110aWrapper.getElectionIdentifier110a();
    wahl.setWahltermin(wahlterminFactory.create());
    if (electionIdentifier110a.getElectionDate() != null) {
      wahl.getWahltermin().setWahldatum(electionIdentifier110a.getElectionDate().toGregorianCalendar().getTime());
    }

    // ELECT-13642
    if (electionIdentifier110a.getElectionDomain() != null) {
      NlWahlCustomData customData = wahl.getCustomData(NlWahlCustomData.class);
      customData.setElectionDomainId(electionIdentifier110a.getElectionDomain().getId());
      customData.setElectionDomain(electionIdentifier110a.getElectionDomain().getValue());
    }

    wahl.setName(electionIdentifier110a.getElectionName());
    wahl.setKennzeichen(electionIdentifier110a.getId());
    wahl.setWahlart(new Wahlart());
    if (eml110aWrapper.getElection().getPreferenceThreshold() != null) {
      wahl.setQuorumProzenthuerde(new BigDecimal(eml110aWrapper.getElection().getPreferenceThreshold().longValue()));
    }
    NlWahlart nlWahlart = nlWahlartEmlMapper.getWahlart(electionIdentifier110a.getElectionCategory(), electionIdentifier110a.getElectionSubcategory());
    setNlWahlart(wahl.getWahlart(), nlWahlart);

    return wahl;
  }

  public ElectionSubcategoryType getElectionSubcategory(EmlWrapper emlWrapper) {
    return new Eml110aWrapper(emlWrapper.getContent()).getElectionIdentifier110a().getElectionSubcategory();
  }

  public void setNlWahlart(Wahlart wahlart, NlWahlart nlWahlart) {
    wahlart.setName(nlWahlart.getWahlartName());
    wahlart.setKennzeichen(nlWahlart.getKennzeichen());
  }

  public EmlWrapper mappe(Wahldaten<Wahl, Gebiet> wahldaten, Gebiet gebiet) {

    EmlWrapper eml = EmlWrapper.createEmlNoCanonicalization("110a");
    Eml110aWrapper eml110 = new Eml110aWrapper(eml.getContent());

    eml.getEmlConfig().setPrettyPrint(true); // ELECTVAPP-984

    Wahl wahl = wahldaten.getWahl();
    ElectionIdentifierStructure110A electionIdentifier110a = eml110.getElectionIdentifier110a();
    mappeWahldaten(wahl, gebiet, wahldaten::getGebiete, electionIdentifier110a, false);

    // ELECTVAPP-967
    eml110.getElection().setPreferenceThreshold(BigInteger.valueOf(NlWahlart.valueOf(wahl).getPreferencialBarrierNumerator(gebiet.getAnzahlSitze())));

    if (gebiet.getAnzahlSitze() != null) {
      eml110.getElection().setNumberOfSeats(BigInteger.valueOf(gebiet.getAnzahlSitze()));
    }

    setContestIdentifier(gebiet, eml110, wahl, electionIdentifier110a);

    mappeGebiete(eml110, wahldaten, gebiet);

    List<PolitischeGruppe> gruppenMitZugelassenenListen = wahldaten.getPolitischeGruppenZugelassenSortiertNachPositionAufGebiet(wahldaten.getWurzelgebiet());
    // ELECT-9943 nur PolitischeGruppen mit zugelassenen Listen exportieren (wenn Listen vorhanden)
    if (gruppenMitZugelassenenListen.isEmpty()) {
      mappeParteien(eml110, wahldaten.getPolitischeGruppen());
    } else {
      mappeParteien(eml110, gruppenMitZugelassenenListen);
    }

    return eml;
  }

  private void setContestIdentifier(Gebiet gebiet, Eml110aWrapper eml110, Wahl wahl, ElectionIdentifierStructure110A electionIdentifier110a) {
    NlWahlart nlWahlartFuerContestIdentifier = NlWahlart.valueOf(wahl);
    if (ElectionSubcategoryType.PS_1.equals(electionIdentifier110a.getElectionSubcategory())) {
      nlWahlartFuerContestIdentifier = NlWahlart.PROVINCIALE_STATEN1; // Spezialbehandlung PS1 ELECT-13568
    } else if (ElectionSubcategoryType.PS_2.equals(electionIdentifier110a.getElectionSubcategory())) {
      nlWahlartFuerContestIdentifier = NlWahlart.PROVINCIALE_STATEN2;
    }

    String eml110ContestIdentifierLautWahlart = nlWahlartFuerContestIdentifier.getEml110ContestIdentifier();
    if (StringUtils.isBlank(eml110ContestIdentifierLautWahlart)) {
      // ELECT-13512 krude AB-Logik, man nehme hier nicht die ID der Watertshap, sondern die ID des Wahlkreises, die eigentlich immer 1 ist...
      if (gebiet.isListenwahlgebiet()) {
        eml110.getElection().getContest().getContestIdentifier().setId(gebiet.getNummer());
      } else {
        Set<Gebiet> untergeordneteGebiete = gebiet.getUntergeordneteGebiete();
        if (untergeordneteGebiete.size() == 1) {
          eml110.getElection().getContest().getContestIdentifier().setId(untergeordneteGebiete.iterator().next().getNummer());
        } else {
          eml110.getElection().getContest().getContestIdentifier().setId(gebiet.getNummer()); // fallback
        }
      }
    } else {
      eml110.getElection().getContest().getContestIdentifier().setId(eml110ContestIdentifierLautWahlart);
    }
  }

  public void mappeWahldaten(Wahl wahl, Gebiet gebiet, Supplier<List<Gebiet>> alleGebiete, ElectionIdentifierStructure110A electionIdentifier110a, boolean skipNominationDate) {
    electionIdentifier110a.setElectionDate(XmlHelper.mappeDateTimeToCalendar(wahl.getWahldatum()));
    if (!skipNominationDate) {
      electionIdentifier110a.setNominationDate(XmlHelper.mappeDateTimeToCalendar(NlWahlart.getNominationDate(wahl)));
    }
    electionIdentifier110a.setElectionName(wahl.getName());
    electionIdentifier110a.setId(wahl.getKennzeichen());
    electionIdentifier110a.setElectionCategory(nlWahlartEmlMapper.bestimmeElectionCategory(wahl.getWahlart()));
    electionIdentifier110a.setElectionSubcategory(nlWahlartEmlMapper.bestimmeElectionSubcategory(wahl, gebiet, alleGebiete));

    ElectionDomain electionDomain = emlCommonMapper.bestimmeElectionDomain(wahl, gebiet);
    if (electionDomain != null) {
      electionIdentifier110a.setElectionDomain(electionDomain);
    }
  }

  private void mappeParteien(Eml110aWrapper eml110, List<PolitischeGruppe> politischeGruppen) {
    /*ELECTVAPP-1509*/
    List<PolitischeGruppe> echtePolitischeGruppenSortiert = politischeGruppen.stream().filter(g -> !new NlPolitischeGruppe(g).isBlancoListe())
        .sorted(this::compareParteien).collect(Collectors.toList());

    for (PolitischeGruppe g : echtePolitischeGruppenSortiert) {
      RegisteredParty registeredParty = eml110.addRegisteredParty(g.getName());
      if (g.getName() != null && g.getName().contains("  ")) {
        registeredParty.setSpace("preserve"); // ELECT-13609
      }

    }
  }

  private int compareParteien(PolitischeGruppe u, PolitischeGruppe v) {
    // Parteien in der Reihenfolge ihres numerischen Schlüssels exportieren (so wurden sie auch importiert)
    Long uLong = asLong(u.getSchluessel());
    Long vLong = asLong(v.getSchluessel());

    if (uLong != null && vLong != null) {
      return uLong.compareTo(vLong);
    }

    if (uLong == null) {
      if (vLong == null) {
        return 0;
      }
      return 1;
    }
    return -1;

  }

  @Nullable
  private Long asLong(String s) {
    if (StringUtils.isBlank(s)) {
      return null;
    }
    try {
      return Long.parseLong(s);
    } catch (Exception e) {
      return null;
    }
  }

  private void mappeGebiete(Eml110aWrapper eml110, Wahldaten<Wahl, Gebiet> wahldaten, Gebiet gebiet) {
    List<Gebiet> untergebiete = wahldaten.getGebiete().stream().filter(untergebiet -> untergebiet.isEqualOderUntergebietVon(gebiet)).collect(Collectors.toList());

    for (Gebiet untergebiet : untergebiete) {
      Short nummer = getNummerVonGebietFuerEml110(untergebiet);
      RegionCategoryType type = nlGebietsartEmlMapper.mappeGebietsart(untergebiet.getGebietsart());
      String name = untergebiet.getName();
      Short uebergeordnetNummer = null;
      NlGebietCustomData customData = untergebiet.getCustomData(NlGebietCustomData.class);
      RegionCategoryType uebergeordnetType = null;
      if (untergebiet.getUebergeordnetesGebiet() != null && untergebiet.getUebergeordnetesGebiet().isEqualOderUntergebietVon(gebiet)) {
        uebergeordnetNummer = getNummerVonGebietFuerEml110(untergebiet.getUebergeordnetesGebiet());
        uebergeordnetType = nlGebietsartEmlMapper.mappeGebietsart(untergebiet.getUebergeordnetesGebiet().getGebietsart());
      }

      Region region = eml110.addRegion(nummer, type, name, uebergeordnetNummer, uebergeordnetType,
          untergebiet.getCustomData(NlGebietCustomData.class).isRomanNumerals());

      addCommittee(region, CommitteeCategoryType.CSB, customData.isCsb(), customData.getCsbName(), customData.isCsbCentralSubmission());
      addCommittee(region, CommitteeCategoryType.HSB, customData.isHsb(), customData.getHsbName(), customData.isHsbCentralSubmission());
      addCommittee(region, CommitteeCategoryType.PROV_SB, customData.isProvsb(), customData.getProvsbName(), customData.isProvsbCentralSubmission());
      addCommittee(region, CommitteeCategoryType.PSB, customData.isPsb(), customData.getPsbName(), customData.isPsbCentralSubmission());

      if (customData.isFrysianExportAllowed()) {
        region.setFrysianExportAllowed(true);
      }

      // region.setFrysianExportAllowed(NlGebietsart.from(untergebiet).isFrysian()); // ELECT-9671
    }
  }

  @Nullable
  private Short getNummerVonGebietFuerEml110(Gebiet untergebiet) {
    if (untergebiet.getUebergeordnetesGebiet() == null && NlGebietsart.from(untergebiet).isStaat()) {
      return null; // ELECT-13533
    }
    return Short.valueOf(untergebiet.getNummer());
  }

  private void addCommittee(Region region, CommitteeCategoryType committeeCategoryType, boolean isCommittee, String committeeName, boolean centralSubmission) {
    if (isCommittee) {
      Committee committee = new ObjectFactory().createCommittee();
      committee.setCommitteeCategory(committeeCategoryType);
      if (StringUtils.isNotBlank(committeeName)) {
        committee.setCommitteeName(committeeName);
      }
      if (centralSubmission) {
        committee.setAcceptCentralSubmissions(Boolean.TRUE);
      }
      region.getCommittee().add(committee);
    }
  }

  public List<String> getGebieteZurAuswahl(EmlWrapper emlWrapper, Predicate<NlGebietsart> nlGebietsartBeachten) {
    return new Eml110aWrapper(emlWrapper.getContent()).getRegions().stream().filter(r -> r.getRegionCategory() != null)
        .filter(region -> nlGebietsartBeachten.test(nlGebietsartEmlMapper.getNlGebietsart(region.getRegionCategory(), region.isFrysianExportAllowed())))
        .map(this::getName)
        .collect(Collectors.toList());
  }

  public List<Gebiet> mappeGebiete(EmlWrapper emlWrapper, Wahl wahl, @Nullable String nameOberstesGebiet, List<WahlParameter> wahlparameter, Mandant mandant) {
    List<Gebiet> alleGebiete = new ArrayList<>();
    List<Region> regions = new Eml110aWrapper(emlWrapper.getContent()).getRegions();

    NlWahlart nlWahlart = NlWahlart.valueOf(wahl.getWahlart());

    Map<GebietTK, Gebiet> gebieteMap = new HashMap<>();
    Map<NlGebietsart, Gebietsart> gebietsartMap = new EnumMap<>(NlGebietsart.class);
    Map<Region, Gebiet> gebieteNachRegion = new HashMap<>();
    Map<Gebiet, Region> regionenNachGebiet = new HashMap<>();
    Map<RegionCategoryType, AtomicLong> nummernGeneratorFuerGebietsart = new EnumMap<>(RegionCategoryType.class);

    Map<RegionCategoryType, Map<String, Boolean>> frysianMap = new EnumMap<>(RegionCategoryType.class);
    String nummerOberstesGebiet = null;
    for (Region region : regions) {
      RegionCategoryType regionCategory = region.getRegionCategory();
      if (regionCategory != null) {
        // ELECT-13397 kommt nur bei AB-Wahlen vor
        if (nlSystemProperties.getDefaultElectionLevel(mandant).isPSB() && RegionCategoryType.WATERSCHAP.equals(regionCategory)) {
          wahl.getCustomData(NlWahlCustomData.class).setAbWatershap(region.getRegionName());
        }
        Gebiet gebiet = gebietFactory.create();
        alleGebiete.add(gebiet);
        gebieteNachRegion.put(region, gebiet);
        regionenNachGebiet.put(gebiet, region);
        gebiet.setWahl(wahl);
        gebiet.setName(region.getRegionName());
        if (region.getRegionNumber() == null) {
          // das oberste Gebiet hat in alten EML-Dateien keine Nummer
          gebiet.setNummer(Long.toString(nummernGeneratorFuerGebietsart.computeIfAbsent(regionCategory, x -> new AtomicLong(0L)).incrementAndGet()));
        } else {
          gebiet.setNummer(region.getRegionNumber().toString());
        }

        if (region.getSuperiorRegionCategory() == null && region.getSuperiorRegionNumber() == null) {
          nummerOberstesGebiet = gebiet.getNummer();
        }

        NlGebietCustomData customData = gebiet.getCustomData(NlGebietCustomData.class);
        customData.setRomanNumerals(region.isRomanNumerals());
        customData.setFrysianExportAllowed(region.isFrysianExportAllowed());

        NlGebietsart nlGebietsart = bestimmeNlGebietsart(nlWahlart, region, regionCategory);

        gebiet.setGebietsart(gebietsartMap.computeIfAbsent(nlGebietsart, x -> nlGebietsartEmlMapper.mappeGebietsart(nlGebietsart)));
        gebieteMap.put(gebiet.getGebietTK(), gebiet);

        frysianMap.computeIfAbsent(regionCategory, x -> new HashMap<>()).put(gebiet.getNummer(), region.isFrysianExportAllowed());

        if (region.getCommittee() != null) {
          for (Committee committee : region.getCommittee()) {
            CommitteeCategoryType committeeCategory = committee.getCommitteeCategory();
            if (committeeCategory != null) {
              String committeeName = committee.getCommitteeName();
              if (StringUtils.isBlank(committeeName)) {
                committeeName = gebiet.getName();
              }
              switch (committeeCategory) {
                case CSB:
                  gebiet.setExterneId(
                      committeeName); // Name CSB https://support.elect-it.com/wiki/display/ELECT/Datenmodell+-+NL+spezifisches+Mapping deprecated
                  customData.setCsb(true);
                  customData.setCsbName(committeeName);
                  customData.setCsbCentralSubmission(Boolean.TRUE.equals(committee.isAcceptCentralSubmissions()));
                  break;
                case HSB:
                  customData.setHsb(true);
                  customData.setHsbName(committeeName);
                  customData.setHsbCentralSubmission(Boolean.TRUE.equals(committee.isAcceptCentralSubmissions()));
                  break;
                case PROV_SB:
                  customData.setProvsb(true);
                  customData.setProvsbName(committeeName);
                  customData.setProvsbCentralSubmission(Boolean.TRUE.equals(committee.isAcceptCentralSubmissions()));
                  break;
                case PSB:
                  customData.setPsb(true);
                  customData.setPsbName(committeeName);
                  customData.setPsbCentralSubmission(Boolean.TRUE.equals(committee.isAcceptCentralSubmissions()));
                  break;
                default:
                  throw new IllegalArgumentException("Unknown: " + committeeCategory);
              }
            }
          }
        }
      }
    }

    for (Region region : regions) {
      RegionCategoryType superiorRegionCategory = region.getSuperiorRegionCategory();
      Short superiorRegionNumber = region.getSuperiorRegionNumber();
      if (superiorRegionCategory != null) {
        Gebiet gebiet = gebieteNachRegion.get(region);
        if (gebiet != null) {

          String parentNummer;
          if (superiorRegionNumber == null) {
            AtomicLong genericNummer = nummernGeneratorFuerGebietsart.get(superiorRegionCategory);
            if (genericNummer == null) {
              if (nummerOberstesGebiet != null) {
                parentNummer = nummerOberstesGebiet; // ELECT-13616
              } else {
                continue;
              }
            } else {
              parentNummer = Long.toString(genericNummer.get());
            }
          } else {
            parentNummer = superiorRegionNumber.toString();
          }

          NlGebietsart nlGebietsart = nlGebietsartEmlMapper.getNlGebietsart(superiorRegionCategory,
              frysianMap.getOrDefault(superiorRegionCategory, Collections.emptyMap()).getOrDefault(parentNummer, false));
          Gebietsart parentGebietsart = gebietsartMap.computeIfAbsent(nlGebietsart, x -> nlGebietsartEmlMapper.mappeGebietsart(nlGebietsart));

          GebietTK parentTk = new GebietTK(parentNummer, parentGebietsart.getGebietsartTK());
          gebiet.setUebergeordnetesGebiet(gebieteMap.get(parentTk));
        }
      }
    }

    List<Gebiet> result = filterAusgewaehltesGebiet(alleGebiete, nameOberstesGebiet, wahlparameter, regionenNachGebiet);

    if (new Eml110aWrapper(emlWrapper.getContent()).getElection().getNumberOfSeats() != null) {
      List<Gebiet> oberstesGebiete = result.stream().filter(g -> !g.hasUebergeordnetesGebiet()).collect(Collectors.toList());
      if (oberstesGebiete.size() == 1) {
        // Die Anzahl der Sitze erstmal an das oberste Gebiet
        // später an die Wahlgebiete: de.ivu.elect.business.wahl.boundary.NlWahlImportHelper.postCreateGebiete
        Gebiet oberstesGebiet = oberstesGebiete.iterator().next();
        oberstesGebiet.setAnzahlSitze(new Eml110aWrapper(emlWrapper.getContent()).getElection().getNumberOfSeats().longValue());
      }
    }
    return result;
  }

  private NlGebietsart bestimmeNlGebietsart(NlWahlart nlWahlart, Region region, RegionCategoryType regionCategory) {
    NlGebietsart nlGebietsart = nlGebietsartEmlMapper.getNlGebietsart(regionCategory, region.isFrysianExportAllowed());

    if (NlGebietsart.GEMEENTE.equals(nlGebietsart) && NlWahlart.EILANDSRAAD.equals(nlWahlart)) {
      // ELECT-13497
      LOG.info("GEMEENTE => OPENBAAR_LICHAAM");
      return NlGebietsart.OPENBAAR_LICHAAM;
    }
    return nlGebietsart;
  }

  private List<Gebiet> filterAusgewaehltesGebiet(List<Gebiet> alleGebiete, @Nullable String nameOberstesGebiet, List<WahlParameter> wahlparameterList, Map<Gebiet, Region> regionenNachGebiet) {
    if (StringUtils.isBlank(nameOberstesGebiet)) {
      return alleGebiete;
    }

    Gebiet oberstesGebiet = findeGebiet(alleGebiete, nameOberstesGebiet, regionenNachGebiet);

    if (oberstesGebiet == null) {
      throw new IllegalArgumentException("Gebiet nicht gefunden: " + nameOberstesGebiet);
    }

    Wahl wahl = oberstesGebiet.getWahl();

    boolean wahlkreisGesetzt = false; // ELECT-9783
    for (Gebiet gebiet : alleGebiete) {
      // ELECT-9687

      if (NlGebietsart.from(gebiet).isKieskring() && gebiet.isInGebietshierarchieUebergeordnetesGebietVon(oberstesGebiet)) {
        addWahlparameter(wahlparameterList, wahl, gebiet, DOKUMENT_PLATZHALTER_WAHLKREIS_NAME, DOKUMENT_PLATZHALTER_WAHLKREIS_NUMMER);
        wahlkreisGesetzt = true;
      }
      // ELECT-9747
      if (NlGebietsart.from(gebiet).isProvinz() && gebiet.isInGebietshierarchieUebergeordnetesGebietVon(oberstesGebiet)) {
        addWahlparameter(wahlparameterList, wahl, gebiet, DOKUMENT_PLATZHALTER_BUNDESLAND_NAME, null);
      }
      if (NlGebietsart.from(gebiet).isGemeinde() && gebiet.isInGebietshierarchieUebergeordnetesGebietVon(oberstesGebiet)) {
        addWahlparameter(wahlparameterList, wahl, gebiet, DOKUMENT_PLATZHALTER_GEMEINDE_NAME, DOKUMENT_PLATZHALTER_GEMEINDE_NUMMER);
      }
    }

    for (Gebiet gebiet : alleGebiete) {
      if (NlGebietsart.isCsb(gebiet/*, alleGebiete*/)) {
        addWahlparameter(wahlparameterList, wahl, gebiet, DOKUMENT_PLATZHALTER_WAHLBEHOERRDE_NAME, null);
        if (!wahlkreisGesetzt /* ELECT-9783 in den Wahlkreis den Gemeindenamen setzen, wenn nicht bereits gesetzt */) {
          addWahlparameter(wahlparameterList, wahl, gebiet, DOKUMENT_PLATZHALTER_WAHLKREIS_NAME, DOKUMENT_PLATZHALTER_WAHLKREIS_NUMMER);
        }
      }
    }


    addMasterElectionTree(oberstesGebiet, oberstesGebiet.getUebergeordnetesGebiet());


    oberstesGebiet.setUebergeordnetesGebiet(null); // neues Wurzelgebiet...

    return alleGebiete.stream().filter(gebiet -> gebiet.isInGebietshierarchieUntergeordnetesGebietVon(oberstesGebiet)).collect(Collectors.toCollection(() -> new ArrayList<>(alleGebiete.size())));
  }

  private void addMasterElectionTree(Gebiet gebiet, Gebiet uebergeordnetesGebiet) {
    if (uebergeordnetesGebiet==null){
      return;
    }
    gebiet.getCustomData(NlGebietCustomData.class).addUebergeornetesGebietInMasterElectionTree(uebergeordnetesGebiet);
    addMasterElectionTree(gebiet, uebergeordnetesGebiet.getUebergeordnetesGebiet());
  }

  private void addWahlparameter(List<WahlParameter> wahlparameterList, Wahl wahl, Gebiet gebiet, WahlParameterWerte dokumentPlatzhalterName, @Nullable WahlParameterWerte dokumentPlatzhalterNummer) {
    WahlParameter parameterName = new WahlParameter();
    parameterName.setWahl(wahl);
    parameterName.setSchluessel(dokumentPlatzhalterName.getKey());
    parameterName.setWert(gebiet.getName());
    wahlparameterList.add(parameterName);

    if (dokumentPlatzhalterNummer != null) {
      WahlParameter parameterNummer = new WahlParameter();
      parameterNummer.setWahl(wahl);
      parameterNummer.setSchluessel(dokumentPlatzhalterNummer.getKey());
      parameterNummer.setWert(gebiet.getNummer());
      if (gebiet.getCustomData(NlGebietCustomData.class).isRomanNumerals()) {
        try {
          parameterNummer.setWert(Roman.toRoman(Long.parseLong(gebiet.getNummer()))); // ELECT-13604
        } catch (Exception e) {
          LOG.warn("Fehler beim Setzen der römischen Zahl: " + e.getMessage() + ", " + gebiet.getNummer());
        }
      }
      wahlparameterList.add(parameterNummer);
    }
  }

  @Nullable
  private Gebiet findeGebiet(List<Gebiet> result, String nameOberstesGebiet, Map<Gebiet, Region> regionenNachGebiet) {
    for (Gebiet gebiet : result) {
      Region region = regionenNachGebiet.get(gebiet);
      if (nameOberstesGebiet.equals(getName(region))) {
        return gebiet;
      }
    }
    return null;
  }

  private String getName(Region region) {
    if (region == null) {
      return "";
    }

    return region.getRegionName() + "|" + region.getRegionCategory().name();
  }

  public List<Gruppe> mappeGruppen(EmlWrapper emlWrapper, Wahl wahl) {
    List<Gruppe> result = new ArrayList<>();
    long position = 1;
    for (RegisteredParty registeredParty : new Eml110aWrapper(emlWrapper.getContent()).getRegisteredParties()) {
      PolitischeGruppe politischeGruppe = politischeGruppeFactory.create();
      result.add(politischeGruppe);

      NlPolitischeGruppe nlPolitischeGruppe = new NlPolitischeGruppe(politischeGruppe);
      nlPolitischeGruppe.setWahl(wahl);
      nlPolitischeGruppe.setGruppeArt(NlPolitischeGruppe.GRUPPE_ART_DEFAULT);
      nlPolitischeGruppe.setName(registeredParty.getRegisteredAppellation());
      nlPolitischeGruppe.setRelativePosition(position);
      nlPolitischeGruppe.setVorgegebenePosition(position);

      position++;
    }

    return result;
  }
}
