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

import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Supplier;
import java.util.stream.Collectors;

import javax.inject.Inject;

import de.ivu.elect.business.gebietsbaum.boundary.GebietExtendedDAO;
import de.ivu.elect.business.gebietsbaum.control.GebietByPositionComparator;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gebietsbaum.entity.Wahldurchfuehrungsgebiet;
import de.ivu.elect.business.stimmeingabe.entity.Ergebniseingang;
import de.ivu.elect.business.stimmeingabe.entity.Ergebniseingangsart;
import de.ivu.elect.business.wahl.boundary.WahlExtendedDAO;
import de.ivu.elect.business.wahl.entity.Wahl;
import de.ivu.elect.business.wahl.entity.WahlParameterExtendedLoader;
import de.ivu.elect.business.wahl.entity.WahlParameterWerte;
import de.ivu.elect.business.wahllokalsystem.entity.QWahllokalsystemStatus;
import de.ivu.elect.business.wahllokalsystem.entity.WahllokalsystemStatus;
import de.ivu.elect.business.wahllokalsystem.entity.WahllokalsystemStatusDokumentEventType;
import de.ivu.elect.business.wahllokalsystem.entity.WahllokalsystemStatusOnline;
import de.ivu.elect.business.wahllokalsystem.entity.WahllokalsystemStimmbezirksart;
import de.ivu.elect.jee.entity.QueryDsl;
import de.ivu.elect.jee.presentation.dynamicform.BooleanFilterKriterium;

public class WahllokalsystemStatusSearch extends AbstractWahllokalsystemStatusSearch {

  @Inject
  private transient WahlExtendedDAO wahlExtendedDAO;

  @Inject
  private transient WahlParameterExtendedLoader wahlParameterExtendedLoader;

  @Inject
  private transient GebietExtendedDAO gebietExtendedDAO;

  @Override
  protected List<WahllokalsystemStatus> querySearchResult(WahllokalsystemStatusSearchParameters searchParameters) {

    if (searchParameters.getWahl() == null || searchParameters.getGebiet() == null) {
      return Collections.emptyList();
    }

    QueryDsl qdsl = dao.getQueryDsl();

    QWahllokalsystemStatus wahllokalsystemStatus = QWahllokalsystemStatus.wahllokalsystemStatus;
    qdsl.from(wahllokalsystemStatus);

    qdsl.eq(wahllokalsystemStatus.wahl, searchParameters.getWahl());
    qdsl.eqIfNotNull(wahllokalsystemStatus.ausstattung, searchParameters.getWahllokalsystemStatus().getAusstattung());

    List<WahllokalsystemStatus> entities = qdsl.list(wahllokalsystemStatus);

    GebietByPositionComparator gebComp = new GebietByPositionComparator();

    Date now = new Date();
    Long onlineTimeout = getOnlineTimeout(wahlExtendedDAO.attach(searchParameters.getWahl()));

    List<WahllokalsystemStatus> result = entities.stream()
            .filter(s -> filterStimmbezirk(s, searchParameters.getWahllokalsystemStimmbezirksart()))
            .filter(s -> s.getGebiet().isInGebietshierarchieUntergeordnetesGebietVon(searchParameters.getGebiet()))
            .filter(s -> filterOnline(s, searchParameters.getStatusOnline(), now, onlineTimeout))
            .sorted((u, v) -> gebComp.compare(u.getGebiet(), v.getGebiet())).collect(Collectors.toList());

    if (isDokumentVorhandenKriteriumVorhanden(searchParameters)) {
      result.removeIf(s -> !filterDokumentStatus(s, searchParameters));
    }

    return result;
  }

  private boolean isDokumentVorhandenKriteriumVorhanden(WahllokalsystemStatusSearchParameters searchParameters) {
    return searchParameters.getWahlFurDokumentVorhandenSuche() != null && searchParameters.getDokumentVorhandenKriterium().values().stream()
        .anyMatch(Objects::nonNull);
  }

  private boolean filterDokumentStatus(WahllokalsystemStatus status, WahllokalsystemStatusSearchParameters searchParameters) {
    Wahl wahlFurDokumentVorhandenSuche = searchParameters.getWahlFurDokumentVorhandenSuche();
    List<Map.Entry<WahllokalsystemStatusDokumentEventType, BooleanFilterKriterium>> dokumentEntries = searchParameters.getDokumentVorhandenKriterium()
        .entrySet().stream().filter(e -> e.getValue() != null).collect(Collectors.toList());

    return dokumentEntries.isEmpty() ||
        filterDokumentStatus(status.getWahllokalsystemStatusAndereWahl(wahlFurDokumentVorhandenSuche, dao, gebietExtendedDAO), dokumentEntries);
  }


  private boolean filterDokumentStatus(WahllokalsystemStatus wahllokalsystemStatus, List<Map.Entry<WahllokalsystemStatusDokumentEventType, BooleanFilterKriterium>> dokumentEntries) {
    Map<Supplier<Date>, WahllokalsystemStatusDokumentEventType> zeitpunktDokumente = wahllokalsystemStatus.getZeitpunktDokumente();
    return dokumentEntries.stream().allMatch(entry -> isErfuellt(entry.getKey(), entry.getValue(), zeitpunktDokumente));
  }

  private boolean isErfuellt(WahllokalsystemStatusDokumentEventType eventType, BooleanFilterKriterium kriterium, Map<Supplier<Date>,
      WahllokalsystemStatusDokumentEventType> zeitpunkte) {

    if (kriterium.toBoolean()) {
      return isVorhanden(eventType, zeitpunkte);
    }

    return !isVorhanden(eventType, zeitpunkte);
  }

  private boolean isVorhanden(WahllokalsystemStatusDokumentEventType eventType, Map<Supplier<Date>, WahllokalsystemStatusDokumentEventType> zeitpunkte) {
    return zeitpunkte.entrySet().stream().filter(entry -> eventType.equals(entry.getValue())).anyMatch(entry -> entry.getKey().get() != null);
  }

  private boolean filterStimmbezirk(WahllokalsystemStatus wahllokalsystemStatus, WahllokalsystemStimmbezirksart wahllokalsystemStimmbezirksart) {
    return wahllokalsystemStimmbezirksart == null || wahllokalsystemStatus == null || wahllokalsystemStatus
        .getGebiet() == null || wahllokalsystemStimmbezirksart.isAnzeigen(wahllokalsystemStatus.getGebiet());

  }

  private boolean filterOnline(WahllokalsystemStatus s, Collection<WahllokalsystemStatusOnline> statusOnline, Date now, Long onlineTimeout) {
    return statusOnline == null || statusOnline.contains(s.getWahllokalsystemStatusOnline(now, onlineTimeout));
  }


  private Long getOnlineTimeout(Wahl wahl) {
    if (wahl == null) {
      return null;
    }
    return wahlParameterExtendedLoader.resolveLongValue(wahl, WahlParameterWerte.WAHLLOKALSYSTEM_STATUS_OFFLINE_TIMEOUT_IN_SECONDS);
  }

}
