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

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

import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.core.types.dsl.DateTimePath;
import com.querydsl.core.types.dsl.EntityPathBase;
import com.querydsl.core.types.dsl.Expressions;
import com.querydsl.core.types.dsl.SimpleExpression;
import com.querydsl.core.types.dsl.StringPath;
import com.querydsl.jpa.impl.JPAQuery;

import de.ivu.elect.business.person.entity.Personendaten;
import de.ivu.elect.jee.entity.Entity;

public abstract class AbstractQueryDslAehnlichePersonenFinder<T extends Personendaten, W extends Entity> extends AbstractAehnlichePersonenFinder<T, W> {

  // Oracle unterstützt im IN-Operator derzeit maximal 1000 Ausdrücke
  private static final int ANZAHL_ZUGLEICH_ZU_PRUEFENDER_PERSONEN = 1000;

  private final PersonendatenMapCreator personendatenMapCreator;

  public AbstractQueryDslAehnlichePersonenFinder() {
    super();

    this.personendatenMapCreator = new PersonendatenMapCreator();
  }

  @Override
  protected List<T> findeAehnlichePersonen(W wahl, List<T> personendaten) {
    return findeAehnlichePersonenMitBegrenzterPersonenzahl(wahl, personendaten, ANZAHL_ZUGLEICH_ZU_PRUEFENDER_PERSONEN);
  }

  private List<T> findeAehnlichePersonenMitBegrenzterPersonenzahl(W wahlOderWahltermin, List<T> personendaten, int anzahlZugleichZuPruefenderPersonen) {
    List<T> result = new ArrayList<>();

    for (int fromIndex = 0; fromIndex < personendaten.size(); fromIndex += anzahlZugleichZuPruefenderPersonen) {
      int toIndex = Math.min(personendaten.size(), fromIndex + anzahlZugleichZuPruefenderPersonen);

      result.addAll(findPotentielleDublettenMitBegrenzterPersonenzahl(wahlOderWahltermin, personendaten.subList(fromIndex, toIndex)));
    }

    return result;
  }

  private List<T> findPotentielleDublettenMitBegrenzterPersonenzahl(W wahlOderWahltermin, List<T> personendaten) {
    JPAQuery query = getQuery();

    query.from(getPersonenTable());
    query.where(getWahlOderWahlterminKriterium(wahlOderWahltermin), anyOf(getOderVerknuepfteKriterien(personendaten)));

    return query.fetch();
  }

  /**
   * HINT: liefert der DAO, etwa kandidaturDao.getQueryDsl().getQuery()
   */
  protected abstract JPAQuery< ? > getQuery();

  /**
   * HINT: benutze auto-generierten Code, etwa QKandidatur.kandidatur
   */
  protected abstract EntityPathBase< ? extends T> getPersonenTable();

  /**
   * HINT: hier gegen Name, Geburtsdatum, Ordnungsmerkmal etc. matchen
   *
   * NOTE: ELECT-1315 (keine leeren "IN"-Ausdrücke in Statements verwenden)
   */
  protected abstract List<BooleanExpression> getOderVerknuepfteKriterien(List<T> personendaten);

  /**
   * HINT: hier gegen die Wahl oder den Wahltermin matchen
   */
  protected abstract BooleanExpression getWahlOderWahlterminKriterium(W wahlOderWahltermin);

  private BooleanExpression anyOf(List<BooleanExpression> expressions) {
    return Expressions.anyOf(expressions.toArray(new BooleanExpression[expressions.size()]));
  }

  protected List<BooleanExpression> addKriteriumOrdnungsmerkmal(List<BooleanExpression> kriterien, List<T> personendaten, StringPath ordnungsmerkmal) {
    return addKriteriumIfValuesNotEmpty(kriterien, ordnungsmerkmal, personendatenMapCreator.personenNachOrdnungsmerkmal(personendaten).keySet());
  }

  protected List<BooleanExpression> addKriteriumFamilienname(List<BooleanExpression> kriterien, List<T> personendaten, StringPath familienname, StringPath familiennameNormalisiert) {
    Collection<String> familiennamenNormalisiert = personendatenMapCreator.personenNachFamiliennameNormalisiert(personendaten).keySet();

    if (!familiennamenNormalisiert.isEmpty()) {
      return addKriteriumIfValuesNotEmpty(kriterien, familiennameNormalisiert, familiennamenNormalisiert);
    }

    return addKriteriumIfValuesNotEmpty(kriterien, familienname, personendatenMapCreator.personenNachFamilienname(personendaten).keySet());
  }

  protected List<BooleanExpression> addKriteriumGeburtsdatum(List<BooleanExpression> kriterien, List<T> personendaten, DateTimePath<Date> geburtsdatum) {
    return addKriteriumIfValuesNotEmpty(kriterien, geburtsdatum, personendatenMapCreator.personenNachGeburtsdatum(personendaten).keySet());
  }

  // ELECT-1315 (keine leeren "IN"-Ausdrücke)
  private <P extends SimpleExpression<V>, V> List<BooleanExpression> addKriteriumIfValuesNotEmpty(List<BooleanExpression> kriterien, P path, Collection<V> values) {
    if (!values.isEmpty()) {
      kriterien.add(path.in(values));
    }

    return kriterien;
  }
}
