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

import static de.ivu.elect.jee.entity.EntityUtil.castTo;
import static java.util.stream.Collectors.toSet;

import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.function.Function;
import java.util.function.Predicate;

import de.ivu.elect.business.ergebnis.control.StimmergebnisDaten;
import de.ivu.elect.business.gebietsbaum.entity.EntityMitGebiet;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gebietsbaum.entity.Wahldurchfuehrungsgebiet;
import de.ivu.elect.business.gruppe.entity.PolitischeGruppe;
import de.ivu.elect.business.kandidatur.entity.Direktkandidatur;
import de.ivu.elect.business.kandidatur.entity.Kandidatur;
import de.ivu.elect.business.kandidatur.entity.Listenkandidatur;
import de.ivu.elect.business.person.entity.VerknuepfungsStatus;
import de.ivu.elect.business.sitzberechnung.entity.Sitzberechnungseingabe;
import de.ivu.elect.business.stimmzettel.entity.EntityAufStimmzettelDaten;
import de.ivu.elect.business.wahlvorschlag.entity.Liste;
import de.ivu.elect.business.wahlvorschlag.entity.Listenverbindung;
import de.ivu.elect.was.sitzberechnung.entity.AbstractElectSitzberechnungKandidatur;
import de.ivu.elect.was.sitzberechnung.entity.ElectSitzberechnungDirektkandidatur;
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.ElectSitzberechnungKandidat;
import de.ivu.elect.was.sitzberechnung.entity.ElectSitzberechnungListe;
import de.ivu.elect.was.sitzberechnung.entity.ElectSitzberechnungListenkandidatur;
import de.ivu.elect.was.sitzberechnung.entity.ElectSitzberechnungListenverbindung;

public class ElectSitzberechnungEntitiesMapper extends AbstractElectSitzberechnungMapper {

  public ElectSitzberechnungEntitiesMapper() {
    super();
  }

  public ElectSitzberechnungEntities mapExtern(ElectSitzberechnungContext sitzberechnungContext, Wahldurchfuehrungsgebiet wahlgebiet, Sitzberechnungseingabe< ? > sitzberechnungseingabe,
      Function<Kandidatur, de.ivu.elect.sitzberechnung.Waehlbarkeit> waehlbarkeitExternFunction, Predicate<Kandidatur> waehlerBenanntExternFunction,
      Predicate<Kandidatur> waehlerBenanntSonstigeExternFunction) {
    StimmergebnisDaten stimmergebnisDaten = sitzberechnungseingabe.getStimmergebnisDaten();
    Predicate<EntityMitGebiet> entityInWahlgebietshierarchie = entityMitGebiet -> entityMitGebiet.getGebiet().isInGebietshierarchieVon(wahlgebiet);
    EntityAufStimmzettelDaten entityAufStimmzettelDaten = stimmergebnisDaten.getEntityAufStimmzettelDaten();

    ElectSitzberechnungGebiet wahlgebietExtern = mapGebietExtern(wahlgebiet);

    Collection<ElectSitzberechnungGebiet> gebieteExtern = mapGebieteExtern(stimmergebnisDaten.getGebiete());
    Collection<ElectSitzberechnungGruppe> gruppenExtern = mapGruppenExtern(stimmergebnisDaten.getPolitischeGruppen());

    Collection<ElectSitzberechnungListenverbindung> listenverbindungenExtern = mapListenverbindungenExtern(stimmergebnisDaten.getListenverbindungen());

    Collection<ElectSitzberechnungListe> listenExtern = mapListenExtern(entityAufStimmzettelDaten, gebieteExtern, gruppenExtern, listenverbindungenExtern,
        stimmergebnisDaten.getListenInGebietshierarchie(wahlgebiet));

    Collection<AbstractElectSitzberechnungKandidatur< ? >> kandidaturenExtern = mapKandidaturenExtern(entityAufStimmzettelDaten, gebieteExtern, gruppenExtern, listenExtern, waehlbarkeitExternFunction,
        waehlerBenanntExternFunction, waehlerBenanntSonstigeExternFunction, stimmergebnisDaten.getKandidaturen().stream().filter(entityInWahlgebietshierarchie).collect(toSet()));

    return ElectSitzberechnungEntities.newInstance(wahlgebietExtern, gebieteExtern, gruppenExtern, listenExtern, listenverbindungenExtern, kandidaturenExtern);
  }

  private Collection<ElectSitzberechnungGebiet> mapGebieteExtern(Collection<Wahldurchfuehrungsgebiet> gebiete) {
    Collection<ElectSitzberechnungGebiet> result = new HashSet<>();

    for (Wahldurchfuehrungsgebiet gebiet : gebiete) {
      result.add(mapGebietExtern(gebiet));
    }

    return result;
  }

  protected ElectSitzberechnungGebiet mapGebietExtern(Gebiet gebiet) {
    return new ElectSitzberechnungGebiet(castTo(gebiet, Wahldurchfuehrungsgebiet.class));
  }

  private Collection<ElectSitzberechnungGruppe> mapGruppenExtern(Collection<PolitischeGruppe> politischeGruppen) {
    Collection<ElectSitzberechnungGruppe> result = new HashSet<>();

    for (PolitischeGruppe politischeGruppe : politischeGruppen) {
      result.add(mapGruppeExtern(politischeGruppe));
    }

    return result;
  }

  protected ElectSitzberechnungGruppe mapGruppeExtern(PolitischeGruppe politischeGruppe) {
    return new ElectSitzberechnungGruppe(politischeGruppe);
  }

  private Collection<ElectSitzberechnungListenverbindung> mapListenverbindungenExtern(Collection<Listenverbindung> listenverbindungen) {
    Collection<ElectSitzberechnungListenverbindung> result = new HashSet<>();

    for (Listenverbindung listenverbindung : listenverbindungen) {
      result.add(mapListenverbindungExtern(listenverbindung));
    }

    return result;
  }

  protected ElectSitzberechnungListenverbindung mapListenverbindungExtern(Listenverbindung listenverbindung) {
    return new ElectSitzberechnungListenverbindung(listenverbindung);
  }

  private Collection<ElectSitzberechnungListe> mapListenExtern(EntityAufStimmzettelDaten entityAufStimmzettelDaten, Collection<ElectSitzberechnungGebiet> gebieteExtern,
      Collection<ElectSitzberechnungGruppe> gruppenExtern, Collection<ElectSitzberechnungListenverbindung> listenverbindungenExtern, Collection<Liste> listen) {
    Collection<ElectSitzberechnungListe> result = new HashSet<>();

    Map<String, ElectSitzberechnungGebiet> gebietExternPerKey = mapEntityExternPerKey(gebieteExtern);
    Map<String, ElectSitzberechnungGruppe> gruppeExternPerKey = mapEntityExternPerKey(gruppenExtern);

    Map<String, ElectSitzberechnungListenverbindung> listenverbindungExternPerKey = mapEntityExternPerKey(listenverbindungenExtern);

    for (Liste liste : listen) {
      ElectSitzberechnungGebiet gebietExtern = gebietExternPerKey.get(keyFromEntityIntern(liste.getGebiet()));
      ElectSitzberechnungGruppe gruppeExtern = gruppeExternPerKey.get(keyFromEntityIntern(liste.getGruppe()));

      Listenverbindung listenverbindung = liste.getListenverbindung();
      ElectSitzberechnungListenverbindung listenverbindungExtern = null;

      if (listenverbindung != null) {
        listenverbindungExtern = listenverbindungExternPerKey.get(keyFromEntityIntern(listenverbindung));
      }

      result.add(mapListeExtern(entityAufStimmzettelDaten, gebietExtern, gruppeExtern, listenverbindungExtern, liste));
    }

    return result;
  }

  protected ElectSitzberechnungListe mapListeExtern(EntityAufStimmzettelDaten entityAufStimmzettelDaten, de.ivu.elect.sitzberechnung.Gebiet gebietExtern,
      de.ivu.elect.sitzberechnung.Gruppe gruppeExtern, de.ivu.elect.sitzberechnung.Listenverbindung listenverbindungExtern, Liste liste) {
    return new ElectSitzberechnungListe(liste, entityAufStimmzettelDaten, gebietExtern, gruppeExtern, listenverbindungExtern);
  }

  private Collection<AbstractElectSitzberechnungKandidatur< ? >> mapKandidaturenExtern(EntityAufStimmzettelDaten entityAufStimmzettelDaten, Collection<ElectSitzberechnungGebiet> gebieteExtern,
      Collection<ElectSitzberechnungGruppe> gruppenExtern, Collection<ElectSitzberechnungListe> listenExtern, Function<Kandidatur, de.ivu.elect.sitzberechnung.Waehlbarkeit> waehlbarkeitExternFunction,
      Predicate<Kandidatur> waehlerBenanntExternFunction, Predicate<Kandidatur> waehlerBenanntSonstigeExternFunction, Collection<Kandidatur> kandidaturen) {
    Collection<AbstractElectSitzberechnungKandidatur< ? >> result = new HashSet<>();

    Map<String, ElectSitzberechnungGebiet> gebietExternPerKey = mapEntityExternPerKey(gebieteExtern);
    Map<String, ElectSitzberechnungGruppe> gruppeExternPerKey = mapEntityExternPerKey(gruppenExtern);

    Map<String, ElectSitzberechnungListe> listeExternPerKey = mapEntityExternPerKey(listenExtern);
    Map<String, ElectSitzberechnungKandidat> kandidatExternPerKandidaturKey = new HashMap<>();

    for (Kandidatur kandidatur : kandidaturen) {
      if (!kandidatExternPerKandidaturKey.containsKey(keyFromEntityIntern(kandidatur))) {
        Collection<Kandidatur> kandidaturenIdentischVerknuepft = new HashSet<>(kandidatur.getVerknuepfteKandidaturenByVerknuepfungsStatus(VerknuepfungsStatus.IDENTISCH));
        kandidaturenIdentischVerknuepft.add(kandidatur);

        ElectSitzberechnungKandidat kandidatExtern = mapKandidatExtern(kandidaturenIdentischVerknuepft);

        kandidaturenIdentischVerknuepft.forEach(kandidaturIdentischVerknuepft -> kandidatExternPerKandidaturKey.put(keyFromEntityIntern(kandidaturIdentischVerknuepft), kandidatExtern));
      }
    }

    for (Kandidatur kandidatur : kandidaturen) {
      ElectSitzberechnungGebiet gebietExtern = gebietExternPerKey.get(keyFromEntityIntern(kandidatur.getGebiet()));
      ElectSitzberechnungGruppe gruppeExtern = gruppeExternPerKey.get(keyFromEntityIntern(kandidatur.getGruppe()));

      ElectSitzberechnungKandidat kandidatExtern = kandidatExternPerKandidaturKey.get(keyFromEntityIntern(kandidatur));
      de.ivu.elect.sitzberechnung.Waehlbarkeit waehlbarkeitExtern = waehlbarkeitExternFunction.apply(kandidatur);

      boolean waehlerBenannt = waehlerBenanntExternFunction.test(kandidatur);
      boolean waehlerBenanntSonstige = waehlerBenanntSonstigeExternFunction.test(kandidatur);

      if (kandidatur.isListenkandidatur()) {
        Listenkandidatur listenkandidatur = castTo(kandidatur, Listenkandidatur.class);
        ElectSitzberechnungListenkandidatur listenkandidaturExtern = mapListenkandidaturExtern(listenkandidatur, gebietExtern, gruppeExtern, kandidatExtern, waehlbarkeitExtern, waehlerBenannt,
            waehlerBenanntSonstige);

        listeExternPerKey.get(keyFromEntityIntern(listenkandidatur.getListe())).addKandidatur(listenkandidaturExtern);

        result.add(listenkandidaturExtern);
      } else if (kandidatur.isDirektkandidatur()) {
        Direktkandidatur direktkandidatur = castTo(kandidatur, Direktkandidatur.class);
        ElectSitzberechnungDirektkandidatur direktkandidaturExtern = mapDirektkandidaturExtern(direktkandidatur, gebietExtern, gruppeExtern, kandidatExtern, waehlbarkeitExtern, waehlerBenannt,
            waehlerBenanntSonstige);

        result.add(direktkandidaturExtern);
      }
    }

    return result;
  }

  protected ElectSitzberechnungKandidat mapKandidatExtern(Collection<Kandidatur> kandidaturenIdentischVerknuepft) {
    return new ElectSitzberechnungKandidat(kandidaturenIdentischVerknuepft);
  }

  protected ElectSitzberechnungListenkandidatur mapListenkandidaturExtern(Listenkandidatur listenkandidatur, ElectSitzberechnungGebiet gebietExtern, ElectSitzberechnungGruppe gruppeExtern,
      ElectSitzberechnungKandidat kandidatExtern, de.ivu.elect.sitzberechnung.Waehlbarkeit waehlbarkeitExtern, boolean waehlerBenannt, boolean waehlerBenanntSonstige) {
    return new ElectSitzberechnungListenkandidatur(listenkandidatur, gebietExtern, gruppeExtern, kandidatExtern, waehlbarkeitExtern, waehlerBenannt, waehlerBenanntSonstige);
  }

  protected ElectSitzberechnungDirektkandidatur mapDirektkandidaturExtern(Direktkandidatur direktkandidatur, ElectSitzberechnungGebiet gebietExtern, ElectSitzberechnungGruppe gruppeExtern,
      ElectSitzberechnungKandidat kandidatExtern, de.ivu.elect.sitzberechnung.Waehlbarkeit waehlbarkeitExtern, boolean waehlerBenannt, boolean waehlerBenanntSonstige) {
    return new ElectSitzberechnungDirektkandidatur(direktkandidatur, gebietExtern, gruppeExtern, kandidatExtern, waehlbarkeitExtern, waehlerBenannt, waehlerBenanntSonstige);
  }
}
