
package de.ivu.elect.business.person.control;

import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import javax.inject.Inject;

import de.ivu.elect.business.person.boundary.PersonenVerknuepfungDAO;
import de.ivu.elect.business.person.boundary.PersonendatenDAO;
import de.ivu.elect.business.person.entity.PersonStatus;
import de.ivu.elect.business.person.entity.PersonenVerknuepfung;
import de.ivu.elect.business.person.entity.Personendaten;
import de.ivu.elect.business.person.entity.PersonendatenPK;
import de.ivu.elect.business.person.entity.VerknuepfungsArt;

/* abgeleitete Klassen mit @Stateless annotieren */
public abstract class AbstractPersonenVerknuepfungAktualisierer<T extends Personendaten> {

  @Inject
  private transient PersonendatenDAO personendatenDAO;

  @Inject
  private transient PersonenVerknuepfungDAO personenVerknuepfungDAO;

  protected AbstractPersonenVerknuepfungAktualisierer() {
    super();
  }

  public VerknuepfungsArt getVerknuepfungsArt() {
    return getVerknuepfungBestimmer().getVerknuepfungsArt();
  }

  public void ergaenzeFehlendeVerknuepfungen(Collection< ? extends T> personen) {
    persistiereVerknuepfungen(personen, collectPersonenVerknuepfungenAnzulegen(personen));
  }

  protected Collection<PersonenVerknuepfung> collectPersonenVerknuepfungenAnzulegen(Collection< ? extends T> personen) {
    Collection< ? extends T> pruefmenge = collectPruefmenge(personen); // ELECTDD-706
    Collection< ? extends T> vergleichsmenge = collectVergleichsmenge(pruefmenge);

    return getVerknuepfungBestimmer().bestimmeAnzulegendeVerknuepfungen(pruefmenge, vergleichsmenge);
  }

  protected abstract AbstractVerknuepfungBestimmer<T> getVerknuepfungBestimmer();

  private Collection< ? extends T> collectPruefmenge(Collection< ? extends T> personen) {
    return filterPruefmenge(personen);
  }

  protected List<T> filterPruefmenge(Collection< ? extends T> pruefmenge) {
    return filterZuPruefendePersonen(pruefmenge); // ELECTDD-706
  }

  private Collection< ? extends T> collectVergleichsmenge(Collection< ? extends T> personen) {
    // NOTE, dass bei einer Implementierung auf die Wahl/den Wahltermin der Personen geachtet werden sollte...
    // ELECTMUC-2769 Nur zu prüfende Personenarten als potenzielle Dubletten behandeln
    return filterVergleichsmenge(getAehnlichePersonenFinder().findeAehnlichePersonen(personen));
  }

  protected List<T> filterVergleichsmenge(List<T> vergleichsmenge) {
    return filterZuPruefendePersonen(vergleichsmenge);
  }

  private List<T> filterZuPruefendePersonen(Collection< ? extends T> personen) {
    return personen.stream().filter(this::isZuPruefendePerson).collect(Collectors.toList());
  }

  protected abstract AbstractAehnlichePersonenFinder<T, ? > getAehnlichePersonenFinder();

  protected void persistiereVerknuepfungen(Collection< ? extends T> personen, Collection<PersonenVerknuepfung> personenVerknuepfungen) {
    for (PersonenVerknuepfung personenVerknuepfung : personenVerknuepfungen) {
      personenVerknuepfung.setVerknuepfungsArt(getVerknuepfungBestimmer().getVerknuepfungsArt());

      persistiereVerknuepfung(personenVerknuepfung);
    }

    setzeNochNichtBetrachtetePersonenMitNeuenVerknuepfungenUngeprueft(personen, personenVerknuepfungen);
  }

  protected void persistiereVerknuepfung(PersonenVerknuepfung personenVerknuepfung) {
    getPersonenVerknuepfungDao().persist(personenVerknuepfung);
  }

  protected void setzeNochNichtBetrachtetePersonenMitNeuenVerknuepfungenUngeprueft(Collection< ? extends T> personen, Collection<PersonenVerknuepfung> personenVerknuepfungen) {
    for (Personendaten personendaten : getAlleNochNichtBetrachtetenPersonenMitNeuenVerknuepfungen(personenVerknuepfungen, personen)) {
      personendaten.setPersonStatus(PersonStatus.UNGEPRUEFT);
    }
  }

  private Set<Personendaten> getAlleNochNichtBetrachtetenPersonenMitNeuenVerknuepfungen(Collection<PersonenVerknuepfung> personenVerknuepfungen, Collection< ? extends Personendaten> personen) {
    Set<Personendaten> result = getPersonendatenInVerknuepfungen(personenVerknuepfungen);
    result.removeAll(personen);

    return result;
  }

  private Set<Personendaten> getPersonendatenInVerknuepfungen(Collection<PersonenVerknuepfung> personenVerknuepfungen) {
    Set<Personendaten> personen = new HashSet<>();

    for (PersonenVerknuepfung personenVerknuepfung : personenVerknuepfungen) {
      addIfNotNull(personen, personenVerknuepfung.getPk1());
      addIfNotNull(personen, personenVerknuepfung.getPk2());
    }

    return personen;
  }

  private void addIfNotNull(Set<Personendaten> personen, PersonendatenPK pk) {
    Personendaten personendaten = personendatenDAO.findByPrimaryKey(pk);

    if (isZuPruefendePerson(personendaten)) {
      personen.add(personendaten);
    }
  }

  private boolean isZuPruefendePerson(Personendaten personendaten) {
    return getZuPruefendePersonendatenClasses().stream().anyMatch(personendaten::isInstanceOf);
  }

  public abstract Collection<Class< ? extends Personendaten>> getZuPruefendePersonendatenClasses();

  protected PersonenVerknuepfungDAO getPersonenVerknuepfungDao() {
    return personenVerknuepfungDAO;
  }
}
