package de.ivu.elect.business.personenpruefung.boundary;

import de.ivu.elect.business.person.entity.Personendaten;
import de.ivu.elect.business.personenpruefung.entity.PersonPruefungKonfiguration;
import de.ivu.elect.business.personenpruefung.entity.PersonPruefungKonfigurationPersonentyp;
import de.ivu.elect.business.personenpruefung.entity.PersonPruefungKonfigurationTyp;
import de.ivu.elect.business.wahl.entity.Wahl;
import de.ivu.elect.business.wahl.entity.Wahltermin;

import javax.annotation.Nullable;
import javax.ejb.Singleton;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.enterprise.event.Observes;
import javax.enterprise.event.Reception;
import javax.inject.Inject;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Supplier;
import java.util.stream.Collectors;

@Singleton
public class PersonPruefungKonfigurationCache implements Serializable {

  @Inject
  private transient PersonPruefungKonfigurationDAO dao;

  private Map<Wahltermin, List<PersonPruefungKonfiguration>> cacheByWahltermin = new ConcurrentHashMap<>();
  private Map<Wahl, List<PersonPruefungKonfiguration>> cacheByWahl = new ConcurrentHashMap<>();

  public Optional<PersonPruefungKonfiguration> findPersonPruefungKonfiguration(PersonPruefungKonfigurationTyp typ, PersonPruefungKonfigurationPersonentyp personentyp, Personendaten personendaten) {
    return findPersonPruefungKonfiguration(typ, personentyp, personendaten.getWahltermin(), personendaten.getWahl());
  }

  public Optional<PersonPruefungKonfiguration> findPersonPruefungKonfiguration(PersonPruefungKonfigurationTyp typ, PersonPruefungKonfigurationPersonentyp personentyp, Wahltermin wahltermin, @Nullable Wahl wahl) {
    if (typ == null || personentyp == null || wahltermin == null) {
      return Optional.empty();
    }

    List<PersonPruefungKonfiguration> result;

    if (wahl == null) {
      result = findByWahltermin(wahltermin);
    } else {
      result = findByWahl(wahl);
    }

    result = result.stream().filter(personPruefungKonfiguration -> personPruefungKonfiguration.getTyp().equals(typ)).collect(Collectors.toList());

    // NOTE: Falls für konkreten Personentyp nichts gefunden wird, dann Personentyp ALLE probieren.
    return Optional.ofNullable(result.stream()
        .filter(personPruefungKonfiguration -> personPruefungKonfiguration.getStatus().isAktiv() && personPruefungKonfiguration.getPersonentyp().equals(personentyp))
        .findFirst()
        .orElse(result.stream()
            .filter(personPruefungKonfiguration -> personPruefungKonfiguration.getStatus().isAktiv() && personPruefungKonfiguration.getPersonentyp().equals(PersonPruefungKonfigurationPersonentyp.ALLE))
            .findFirst()
            .orElse(null)
        )
    );
  }

  @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
  public List<PersonPruefungKonfiguration> findByWahltermin(Wahltermin wahltermin) {
    return cacheByWahltermin.computeIfAbsent(wahltermin, w -> findEagerLoading(() -> dao.findByWahltermin(w)));
  }

  @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
  public List<PersonPruefungKonfiguration> findByWahl(Wahl wahl) {
    return cacheByWahl.computeIfAbsent(wahl, w -> findEagerLoading(() -> dao.findByWahl(w)));
  }

  private List<PersonPruefungKonfiguration> findEagerLoading(Supplier<List<PersonPruefungKonfiguration>> finder) {
    List<PersonPruefungKonfiguration> personPruefungKonfigurationen = finder.get();
    personPruefungKonfigurationen.forEach(this::eagerLoad);
    return personPruefungKonfigurationen;
  }

  private void eagerLoad(PersonPruefungKonfiguration personPruefungKonfiguration) {
    personPruefungKonfiguration.getCustomData();
  }

  public void onPersonPruefungKonfigurationChanged(@Observes(notifyObserver = Reception.ALWAYS) PersonPruefungKonfigurationChangeEvent changeEvent) {
    cacheByWahltermin.clear();
    cacheByWahl.clear();
  }
}
