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


import de.ivu.elect.business.ergebnis.control.StimmenPool;
import de.ivu.elect.business.ergebnis.control.StimmergebnisDaten;
import de.ivu.elect.business.ergebnis.entity.Stimmen;
import de.ivu.elect.business.gebietsbaum.entity.GebietPK;
import de.ivu.elect.business.gebietsbaum.entity.Wahldurchfuehrungsgebiet;
import de.ivu.elect.business.gruppe.entity.GruppePK;
import de.ivu.elect.business.gruppe.entity.PolitischeGruppe;
import de.ivu.elect.business.kandidatur.entity.Listenkandidatur;
import de.ivu.elect.business.person.entity.PersonendatenPK;
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.WahlvorschlagPK;
import de.ivu.elect.sitzberechnung.Gebiet;
import de.ivu.elect.sitzberechnung.Gruppe;
import de.ivu.elect.sitzberechnung.Kandidat;
import de.ivu.elect.sitzberechnung.Kandidatur;
import de.ivu.elect.sitzberechnung.Waehlbarkeit;
import de.ivu.elect.sitzberechnung.konflikt.AbstractErgebnisKonflikt;
import de.ivu.elect.sitzberechnung.konflikt.ErgebnisKonflikt;
import de.ivu.elect.was.sitzberechnung.entity.ElectSitzberechnungseingabe;
import de.ivu.elect.was.sitzberechnung.entity.KonfliktEntitaeten;
import de.ivu.elect.was.sitzberechnung.entity.KonfliktMapper;
import de.ivu.elect.was.sitzberechnung.entity.SitzberechnungDirektkandidatur;
import de.ivu.elect.was.sitzberechnung.entity.SitzberechnungGebiet;
import de.ivu.elect.was.sitzberechnung.entity.SitzberechnungGruppe;
import de.ivu.elect.was.sitzberechnung.entity.SitzberechnungKandidat;
import de.ivu.elect.was.sitzberechnung.entity.SitzberechnungListe;
import de.ivu.elect.was.sitzberechnung.entity.SitzberechnungListenkandidatur;
import de.ivu.elect.was.sitzberechnung.entity.SitzberechnungMappingContext;

import java.util.AbstractMap;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

public class SitzberechnungeingabeMapper {

  public ElectSitzberechnungseingabe mappe(Sitzberechnungseingabe<?> sitzberechnungseingabe,
                                           Wahldurchfuehrungsgebiet wahlgebiet,
                                           KonfliktMapper konfliktMapper,
                                           Function<de.ivu.elect.business.kandidatur.entity.Kandidatur, Waehlbarkeit> kandidaturWaehlbarkeitFunction,
                                           SitzberechnungMappingContext sitzberechnungMappingContext) {

    StimmergebnisDaten stimmergebnisDaten = sitzberechnungseingabe.getStimmergebnisDaten();

    Map<GebietPK, SitzberechnungGebiet> gebietMap = mappeGebiete(filterGebiete(stimmergebnisDaten.getGebiete(), wahlgebiet));

    Map<GruppePK, SitzberechnungGruppe> gruppeMap = mappeGruppen(stimmergebnisDaten.getPolitischeGruppen());
    Map<WahlvorschlagPK, SitzberechnungListe> listenMap = mappeListen(stimmergebnisDaten.getListenInGebietshierarchie(wahlgebiet),
            stimmergebnisDaten.getEntityAufStimmzettelDaten(), gebietMap, gruppeMap);

    List<de.ivu.elect.business.kandidatur.entity.Kandidatur> kandidaturenAufGebiet = filterKandidaturenAufGebiet(stimmergebnisDaten.getKandidaturen(), wahlgebiet);
    Map<PersonendatenPK, Kandidat> kandidatenMap = mappeKandidaten(kandidaturenAufGebiet);
    Map<Kandidatur, Long> kandidaturStimmenMap = mappeKandidaturenStimmen(kandidaturenAufGebiet, gebietMap, gruppeMap,
            listenMap, kandidatenMap, stimmergebnisDaten.getStimmen(), kandidaturWaehlbarkeitFunction);

    Set<ErgebnisKonflikt> konflikte = mappeKonflikte(sitzberechnungseingabe.getKonflikte(), konfliktMapper, KonfliktEntitaeten.newInstance(gruppeMap.values(),
            listenMap.values(), kandidaturStimmenMap.keySet(), wahlgebiet), sitzberechnungMappingContext);


    return new ElectSitzberechnungseingabe((Collection) gebietMap.values(), (Collection) listenMap.values(),
            kandidaturStimmenMap, wahlgebiet.getAnzahlSitze(), konflikte);
  }

  private List<de.ivu.elect.business.kandidatur.entity.Kandidatur> filterKandidaturenAufGebiet(List<de.ivu.elect.business.kandidatur.entity.Kandidatur> kandidaturen, Wahldurchfuehrungsgebiet wahlgebiet) {
    return kandidaturen.stream()
            .filter(k -> k.getGebiet().isInGebietshierarchieVon(wahlgebiet))
            .collect(Collectors.toList());
  }

  private List<de.ivu.elect.business.gebietsbaum.entity.Gebiet> filterGebiete(List<Wahldurchfuehrungsgebiet> gebiete, de.ivu.elect.business.gebietsbaum.entity.Gebiet wahlgebiet) {
    return gebiete.stream()
            .filter(g -> g.isInGebietshierarchieVon(wahlgebiet))
            .collect(Collectors.toList());
  }

  private Set<ErgebnisKonflikt> mappeKonflikte(List<de.ivu.elect.business.ergebniskonflikt.entity.ErgebnisKonflikt> konflikte,
                                               KonfliktMapper konfliktMapper, KonfliktEntitaeten konfliktEntitaeten, SitzberechnungMappingContext sitzberechnungMappingContext) {
    return konflikte.stream()
            .map(k -> mappeKonflikt(k, konfliktMapper, konfliktEntitaeten, sitzberechnungMappingContext))
            .collect(Collectors.toSet());
  }

  private ErgebnisKonflikt mappeKonflikt(de.ivu.elect.business.ergebniskonflikt.entity.ErgebnisKonflikt konflikt,
                                         KonfliktMapper konfliktMapper, KonfliktEntitaeten konfliktEntitaeten, SitzberechnungMappingContext sitzberechnungMappingContext) {
    ErgebnisKonflikt ergebnisKonflikt = konfliktMapper.mappeEingabe(konflikt, konfliktEntitaeten, sitzberechnungMappingContext);
    if (ergebnisKonflikt instanceof AbstractErgebnisKonflikt) {
      ((AbstractErgebnisKonflikt) ergebnisKonflikt).setErgebnisKonfliktPersistent(konflikt);
    }
    return ergebnisKonflikt;
  }

  private Map<WahlvorschlagPK, SitzberechnungListe> mappeListen(Set<Liste> listen,
                                                                EntityAufStimmzettelDaten entityAufStimmzettelDaten,
                                                                Map<GebietPK, SitzberechnungGebiet> gebietMap,
                                                                Map<GruppePK, SitzberechnungGruppe> gruppeMap) {
    Map<WahlvorschlagPK, SitzberechnungListe> listeMap = new HashMap<>();
    for (Liste liste : listen) {
      Gebiet gebiet = gebietMap.get(liste.getGebiet().getPk());
      Gruppe gruppe = gruppeMap.get(liste.getGruppe().getPk());
      listeMap.put(liste.getPk(), new SitzberechnungListe(liste, entityAufStimmzettelDaten, gebiet, gruppe));
    }
    return listeMap;
  }

  private Map<GruppePK, SitzberechnungGruppe> mappeGruppen(List<PolitischeGruppe> politischeGruppen) {
    return politischeGruppen.stream()
            .map(gruppe -> new AbstractMap.SimpleImmutableEntry<>(gruppe.getPk(), createSitzberechnungGruppe(gruppe)))
            .collect(Collectors.toMap(AbstractMap.SimpleImmutableEntry::getKey, AbstractMap.SimpleImmutableEntry::getValue));
  }

  protected SitzberechnungGruppe createSitzberechnungGruppe(PolitischeGruppe gruppe) {
    return new SitzberechnungGruppe(gruppe);
  }

  private Map<GebietPK, SitzberechnungGebiet> mappeGebiete(List<de.ivu.elect.business.gebietsbaum.entity.Gebiet> gebiete) {
    return gebiete.stream()
            .map(gebiet -> new AbstractMap.SimpleImmutableEntry<>(gebiet.getPk(), new SitzberechnungGebiet((Wahldurchfuehrungsgebiet) gebiet)))
            .collect(Collectors.toMap(AbstractMap.SimpleImmutableEntry::getKey, AbstractMap.SimpleImmutableEntry::getValue));
  }

  private Map<Kandidatur, Long> mappeKandidaturenStimmen(List<de.ivu.elect.business.kandidatur.entity.Kandidatur> kandidaturen,
                                                         Map<GebietPK, SitzberechnungGebiet> gebietMap,
                                                         Map<GruppePK, SitzberechnungGruppe> gruppeMap,
                                                         Map<WahlvorschlagPK, SitzberechnungListe> listenMap,
                                                         Map<PersonendatenPK, Kandidat> kandidatenMap, StimmenPool stimmen,
                                                         Function<de.ivu.elect.business.kandidatur.entity.Kandidatur, Waehlbarkeit> waehlbarkeitFunction) {
    Map<Kandidatur, Long> kandidaturMap = new HashMap<>();
    for (de.ivu.elect.business.kandidatur.entity.Kandidatur kandidatur : kandidaturen) {
      Gebiet gebiet = gebietMap.get(kandidatur.getGebiet().getPk());
      Gruppe gruppe = gruppeMap.get(kandidatur.getGruppeImGebiet().getGruppe().getPk());
      Waehlbarkeit waehlbarkeit = waehlbarkeitFunction.apply(kandidatur);
      Kandidat kandidat = kandidatenMap.get(kandidatur.getPk());

      if (kandidatur.isListenkandidatur()) {
        mappeListenkandidatur(kandidatur.castTo(Listenkandidatur.class).getEntity(), listenMap, stimmen, kandidaturMap, gebiet, gruppe, waehlbarkeit, kandidat);
      } else if (kandidatur.isDirektkandidatur()) {
        mappeDirektkandidatur(kandidatur
                .castTo(de.ivu.elect.business.kandidatur.entity.Direktkandidatur.class).getEntity(), stimmen, kandidaturMap, gebiet, gruppe, waehlbarkeit, kandidat);
      }
    }
    return kandidaturMap;
  }

  private void mappeDirektkandidatur(de.ivu.elect.business.kandidatur.entity.Direktkandidatur direktkandidatur, StimmenPool stimmen, Map<Kandidatur, Long> kandidaturMap, Gebiet gebiet, Gruppe gruppe, Waehlbarkeit waehlbarkeit, Kandidat kandidat) {
    kandidaturMap.put(new SitzberechnungDirektkandidatur(direktkandidatur, gebiet, gruppe, waehlbarkeit, kandidat),
            getKandidaturStimmen(stimmen.getDirektkandidatStimmen(direktkandidatur.getDirektkandidat())));
  }

  private void mappeListenkandidatur(Listenkandidatur listenkandidatur, Map<WahlvorschlagPK, SitzberechnungListe> listenMap, StimmenPool stimmen, Map<Kandidatur, Long> kandidaturMap, Gebiet gebiet, Gruppe gruppe, Waehlbarkeit waehlbarkeit, Kandidat kandidat) {
    SitzberechnungListenkandidatur sbKandidatur = new SitzberechnungListenkandidatur(listenkandidatur, gebiet, gruppe, waehlbarkeit, kandidat);
    listenMap.get(listenkandidatur.getListe().getPk()).addKandidatur(sbKandidatur);
    kandidaturMap.put(sbKandidatur, getKandidaturStimmen(stimmen.getListenkandidaturstimmen(listenkandidatur)));
  }

  private Long getKandidaturStimmen(Collection<Stimmen> kandidaturStimmenProGebiet) {
    return kandidaturStimmenProGebiet.stream()
            .filter(s -> s.getAnzahl() != null)
            .mapToLong(Stimmen::getAnzahl)
            .sum();
  }

  private Map<PersonendatenPK, Kandidat> mappeKandidaten(List<de.ivu.elect.business.kandidatur.entity.Kandidatur> kandidaturen) {
    Map<PersonendatenPK, Kandidat> kandidaturenMap = new HashMap<>();

    for (de.ivu.elect.business.kandidatur.entity.Kandidatur kandidatur : kandidaturen) {
      if (!kandidaturenMap.containsKey(kandidatur.getPk())) {
        Set<de.ivu.elect.business.kandidatur.entity.Kandidatur> verknuepfteKandidaturen = new HashSet<>(kandidatur.getVerknuepfteKandidaturenByVerknuepfungsStatus(VerknuepfungsStatus.IDENTISCH));
        verknuepfteKandidaturen.add(kandidatur);
        SitzberechnungKandidat kandidat = new SitzberechnungKandidat(verknuepfteKandidaturen);
        verknuepfteKandidaturen.forEach(k -> kandidaturenMap.put(k.getPk(), kandidat));
      }
    }
    return kandidaturenMap;
  }
}
