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

import static java.util.stream.Collectors.toSet;
import static org.apache.commons.lang3.StringUtils.isAllBlank;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.trimToEmpty;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Objects;

import javax.inject.Inject;

import de.ivu.elect.business.gebietsbaum.boundary.GebietExtendedDAO;
import de.ivu.elect.business.gebietsbaum.control.FilterByGebiet;
import de.ivu.elect.business.gebietsbaum.control.FilterByGebietParameter;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gebietsbaum.entity.Wahldurchfuehrungsgebiet;
import de.ivu.elect.business.niederschrift.entity.Niederschrift;
import de.ivu.elect.business.niederschrift.entity.NiederschriftByGebietNummerAndNiederschriftArtComparator;
import de.ivu.elect.business.niederschrift.entity.NiederschriftStatus;
import de.ivu.elect.business.niederschrift.entity.NiederschriftStatusAenderung;
import de.ivu.elect.business.niederschrift.entity.QNiederschrift;
import de.ivu.elect.business.niederschrift.entity.QNiederschriftStatusAenderung;
import de.ivu.elect.business.stimmeingabe.entity.StimmeingabePlausiGebietsart;
import de.ivu.elect.jee.entity.QueryDsl;
import de.ivu.elect.jee.faces.Messages;
import de.ivu.elect.jee.presentation.search.WildcardConverter;

public class NiederschriftSearch extends AbstractNiederschriftSearch {

  @Inject
  private transient Messages messages;

  @Inject
  private transient GebietExtendedDAO gebietExtendedDAO;

  @Inject
  private transient NiederschriftStatusAenderungExtendedDAO niederschriftStatusAenderungExtendedDAO;

  @Inject
  private transient FilterByGebiet<Niederschrift> filterByGebiet;

  public NiederschriftSearch() {
    super();
  }

  @Override
  protected List<Niederschrift> querySearchResult(NiederschriftSearchParameters searchParameters) {
    List<Niederschrift> result = new ArrayList<>();

    Gebiet gebiet = gebietExtendedDAO.attach(searchParameters.getGebiet());

    if (gebiet == null) {
      result.addAll(querySearchResultByNiederschriftStatusAenderung(gebiet, searchParameters));
    } else {
      result.addAll(querySearchResultByNiederschrift(gebiet, searchParameters));
    }

    result = filterByGebiet(result, gebiet, searchParameters.isIncludeUntergebiete());
    result = filterByNeuesteNiederschriftStatusAenderung(result, searchParameters);

    Collections.sort(result, new NiederschriftByGebietNummerAndNiederschriftArtComparator(messages));

    return result;
  }

  // Über Tabelle NiederschriftStatusAenderung suchen, um nicht ALLE Niederschriften zu laden und erst ANSCHLIEßEND nach deren letzte NiederschriftStatusAenderung zu filtern!
  private Collection<Niederschrift> querySearchResultByNiederschriftStatusAenderung(Gebiet gebiet, NiederschriftSearchParameters searchParameters) {
    QNiederschriftStatusAenderung niederschriftStatusAenderung = QNiederschriftStatusAenderung.niederschriftStatusAenderung;

    QueryDsl queryDsl = dao.getQueryDsl();
    queryDsl.from(niederschriftStatusAenderung);

    queryDsl.inIfNotEmpty(niederschriftStatusAenderung.status, searchParameters.getNeuesteNiederschriftStatusAenderungStatus());
    queryDsl.betweenIfNotNull(niederschriftStatusAenderung.zeitstempel, searchParameters.getNeuesteNiederschriftStatusAenderungZeitstempelMin(),
        searchParameters.getNeuesteNiederschriftStatusAenderungZeitstempelMax());

    appendNiederschriftQueryParameters(queryDsl, niederschriftStatusAenderung.niederschrift, gebiet, searchParameters);

    return queryDsl.list(niederschriftStatusAenderung).stream().map(NiederschriftStatusAenderung::getNiederschrift).collect(toSet());
  }

  private Collection<Niederschrift> querySearchResultByNiederschrift(Gebiet gebiet, NiederschriftSearchParameters searchParameters) {
    QNiederschrift niederschrift = QNiederschrift.niederschrift;

    QueryDsl queryDsl = dao.getQueryDsl();
    queryDsl.from(niederschrift);

    appendNiederschriftQueryParameters(queryDsl, niederschrift, gebiet, searchParameters);

    return queryDsl.list(niederschrift);
  }

  private QueryDsl appendNiederschriftQueryParameters(QueryDsl queryDsl, QNiederschrift niederschrift, Gebiet gebiet, NiederschriftSearchParameters searchParameters) {
    queryDsl.eqIfNotNull(niederschrift.wahldurchfuehrungsgebiet.wahltermin.mandant, searchParameters.getMandant());

    if (gebiet != null) {
      queryDsl.eq(niederschrift.wahldurchfuehrungsgebiet.wahl, gebiet.getWahl());
    }

    queryDsl.inIfNotEmpty(niederschrift.niederschriftArt, searchParameters.getNiederschriftArten());

    return queryDsl;
  }

  private List<Niederschrift> filterByNeuesteNiederschriftStatusAenderung(List<Niederschrift> niederschriften, NiederschriftSearchParameters searchParameters) {
    StimmeingabePlausiGebietsart searchParameterNiederschriftWahldurchfuehrungsgebietGebietsart = searchParameters.getNiederschriftWahldurchfuehrungsgebietGebietsart();

    boolean anySearchParameterNiederschriftAvailable = searchParameterNiederschriftWahldurchfuehrungsgebietGebietsart != null;

    String searchParameterNeuesteNiederschriftStatusAenderungRaum = searchParameters.getNeuesteNiederschriftStatusAenderungRaum();
    String searchParameterNeuesteNiederschriftStatusAenderungBesitzer = searchParameters.getNeuesteNiederschriftStatusAenderungBesitzer();

    Collection<NiederschriftStatus> searchParameterNeuesteNiederschriftStatusAenderungStatus = searchParameters.getNeuesteNiederschriftStatusAenderungStatus();

    Date searchParameterNeuesteNiederschriftStatusAenderungZeitstempelMin = searchParameters.getNeuesteNiederschriftStatusAenderungZeitstempelMin();
    Date searchParameterNeuesteNiederschriftStatusAenderungZeitstempelMax = searchParameters.getNeuesteNiederschriftStatusAenderungZeitstempelMax();

    boolean anySearchParameterNeuesteNiederschriftStatusAenderungAvailable = !isAllBlank(searchParameterNeuesteNiederschriftStatusAenderungRaum,
        searchParameterNeuesteNiederschriftStatusAenderungBesitzer) || !searchParameterNeuesteNiederschriftStatusAenderungStatus.isEmpty()
        || searchParameterNeuesteNiederschriftStatusAenderungZeitstempelMin != null || searchParameterNeuesteNiederschriftStatusAenderungZeitstempelMax != null;

    if (!anySearchParameterNiederschriftAvailable && !anySearchParameterNeuesteNiederschriftStatusAenderungAvailable) {
      return niederschriften;
    }

    List<Niederschrift> result = new ArrayList<>(niederschriften.size());

    for (Niederschrift niederschrift : niederschriften) {
      Wahldurchfuehrungsgebiet wahldurchfuehrungsgebiet = niederschrift.getWahldurchfuehrungsgebiet();

      // Filter Niederschrift
      if (anySearchParameterNeuesteNiederschriftStatusAenderungAvailable) {
        if (searchParameterNiederschriftWahldurchfuehrungsgebietGebietsart != null
            && (searchParameterNiederschriftWahldurchfuehrungsgebietGebietsart.isUrnenwahlbezirk() && !wahldurchfuehrungsgebiet.isUrnenwahlgebiet()
                || searchParameterNiederschriftWahldurchfuehrungsgebietGebietsart.isBriefwahlbezirk() && !wahldurchfuehrungsgebiet.isBriefwahlgebiet())) {
          continue;
        }
      }

      // Filter neueste Statusänderung
      if (anySearchParameterNeuesteNiederschriftStatusAenderungAvailable) {
        NiederschriftStatusAenderung neuesteNiederschriftStatusAenderung = niederschrift.findNeuesteNiederschriftStatusAenderung(niederschriftStatusAenderungExtendedDAO);

        if (neuesteNiederschriftStatusAenderung == null) {
          continue;
        }

        if (!valueMatchesSearchPattern(neuesteNiederschriftStatusAenderung.getRaum(), searchParameterNeuesteNiederschriftStatusAenderungRaum)) {
          continue;
        }

        if (!valueMatchesSearchPattern(neuesteNiederschriftStatusAenderung.getBesitzer(), searchParameterNeuesteNiederschriftStatusAenderungBesitzer)) {
          continue;
        }

        if (searchParameterNeuesteNiederschriftStatusAenderungStatus != null && !searchParameterNeuesteNiederschriftStatusAenderungStatus.isEmpty()
            && !searchParameterNeuesteNiederschriftStatusAenderungStatus.contains(neuesteNiederschriftStatusAenderung.getStatus())) {
          continue;
        }

        Date neuesteNiederschriftStatusAenderungZeitstempel = neuesteNiederschriftStatusAenderung.getZeitstempel();

        if (searchParameterNeuesteNiederschriftStatusAenderungZeitstempelMin != null
            && searchParameterNeuesteNiederschriftStatusAenderungZeitstempelMin.after(neuesteNiederschriftStatusAenderungZeitstempel)) {
          continue;
        }

        if (searchParameterNeuesteNiederschriftStatusAenderungZeitstempelMax != null
            && searchParameterNeuesteNiederschriftStatusAenderungZeitstempelMax.before(neuesteNiederschriftStatusAenderungZeitstempel)) {
          continue;
        }
      }

      // alles erfüllt
      result.add(niederschrift);
    }

    return result;
  }

  private boolean valueMatchesSearchPattern(String value, String searchPattern) {
    return Objects.equals(value, searchPattern) || isBlank(searchPattern) || trimToEmpty(value).matches(convertToStringMatcherExpression(searchPattern));
  }

  private String convertToStringMatcherExpression(String searchPattern) {
    return new WildcardConverter().addAndConvertToStringMatcherExpression(searchPattern);
  }

  private List<Niederschrift> filterByGebiet(List<Niederschrift> niederschriften, Gebiet gebiet, boolean includeUntergebiete) {
    List<Niederschrift> result = new ArrayList<>(niederschriften);

    if (gebiet != null) {
      result.retainAll(filterByGebiet.filterByGebiet(new ArrayList<>(niederschriften), createFilterByGebietParameter(gebiet, includeUntergebiete)));
    }

    return result;
  }

  private FilterByGebietParameter createFilterByGebietParameter(Gebiet gebiet, boolean includeUntergebiete) {
    return new FilterByGebietParameter(gebiet, includeUntergebiete);
  }
}
