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

import static java.util.Collections.sort;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import javax.ejb.Stateless;
import javax.inject.Inject;

import com.querydsl.core.types.dsl.EnumPath;

import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gruppe.entity.Gruppe;
import de.ivu.elect.business.kandidatur.entity.Kandidatur;
import de.ivu.elect.business.wahlvorschlag.boundary.WahlvorschlagExtendedDAO;
import de.ivu.elect.business.wahlvorschlag.entity.Wahlvorschlag;
import de.ivu.elect.business.wahlvorschlagsperson.control.WahlvorschlagsPersonByWahlvorschlagAndEingangssequenznummerComparator;
import de.ivu.elect.business.wahlvorschlagsperson.control.WahlvorschlagsPersonByWahlvorschlagsPersonTypAndEingangssequenznummerAndNamenComparator;
import de.ivu.elect.business.wahlvorschlagsperson.entity.AbstractWahlvorschlagsPerson;
import de.ivu.elect.business.wahlvorschlagsperson.entity.QWahlvorschlagsPerson;
import de.ivu.elect.business.wahlvorschlagsperson.entity.WahlvorschlagsPerson;
import de.ivu.elect.business.wahlvorschlagsperson.entity.WahlvorschlagsPersonTyp;
import de.ivu.elect.jee.entity.QueryDsl;
import de.ivu.elect.jee.faces.Messages;

@Stateless
public class WahlvorschlagsPersonSearch extends AbstractWahlvorschlagsPersonSearch {

  private static final Comparator<WahlvorschlagsPerson> DEFAULT_WAHLVORSCHLAGS_PERSON_COMPARATOR = new WahlvorschlagsPersonByWahlvorschlagAndEingangssequenznummerComparator();

  @Inject
  private transient WahlvorschlagExtendedDAO wahlvorschlagDao;

  @Inject
  private transient Messages messages;

  public WahlvorschlagsPersonSearch() {
    super();
  }

  @Override
  protected List<WahlvorschlagsPerson> querySearchResult(WahlvorschlagsPersonSearchParameters searchParameters) {
    if (searchParameters.hasWahlvorschlag() || searchParameters.hasKandidatur()) {
      Wahlvorschlag wahlvorschlag = searchParameters.getWahlvorschlag();
      if (wahlvorschlag == null && searchParameters.hasKandidatur()) {
        wahlvorschlag = searchParameters.getKandidatur().getWahlvorschlag();
      }
      return getSearchResultByWahlvorschlagsPersonTypenAndWahlvorschlag(searchParameters.getWahlvorschlagsPersonTypen(), wahlvorschlag, searchParameters.getKandidatur());
    }

    return getSearchResultBySearchParameters(searchParameters);
  }

  private List<WahlvorschlagsPerson> getSearchResultByWahlvorschlagsPersonTypenAndWahlvorschlag(Collection<WahlvorschlagsPersonTyp> wahlvorschlagsPersonTypen, Wahlvorschlag wahlvorschlag, Kandidatur kandidatur) {
    QWahlvorschlagsPerson wahlvorschlagsPerson = QWahlvorschlagsPerson.wahlvorschlagsPerson;

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

    queryDsl.eqIn(wahlvorschlagsPerson.wahlvorschlagsPersonTyp, wahlvorschlagsPersonTypen);
    queryDsl.eq(wahlvorschlagsPerson.wahlvorschlag, attach(wahlvorschlag));

    List<WahlvorschlagsPerson> result = new ArrayList<>(queryDsl.getQuery().fetch());

    // ELECT-15852
    Predicate<WahlvorschlagsPerson> indiviuelleWahlvorschlagsPersonFilter;
    if (kandidatur == null) {
      indiviuelleWahlvorschlagsPersonFilter = AbstractWahlvorschlagsPerson::isAllgemeineWahlvorschlagsPerson;
    } else {
      indiviuelleWahlvorschlagsPersonFilter = wahlvorschlagsPerson1 -> wahlvorschlagsPerson1.isIndividuelleWahlvorschlagsPerson() && kandidatur.getId().equals(wahlvorschlagsPerson1.getKandidatur().getId());
    }
    result = result.stream().filter(indiviuelleWahlvorschlagsPersonFilter).collect(Collectors.toList());

    // ELECTMUC-444
    sort(result, new WahlvorschlagsPersonByWahlvorschlagsPersonTypAndEingangssequenznummerAndNamenComparator(messages));

    return result;
  }

  private List<WahlvorschlagsPerson> getSearchResultBySearchParameters(WahlvorschlagsPersonSearchParameters searchParameters) {
    QWahlvorschlagsPerson wahlvorschlagsPerson = QWahlvorschlagsPerson.wahlvorschlagsPerson;

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

    EnumPath<WahlvorschlagsPersonTyp> wahlvorschlagsPersonTyp = wahlvorschlagsPerson.wahlvorschlagsPersonTyp;

    if (searchParameters.hasWahlvorschlagsPersonTyp()) {
      queryDsl.eq(wahlvorschlagsPersonTyp, searchParameters.getWahlvorschlagsPersonTyp());
    } else {
      queryDsl.eqIn(wahlvorschlagsPersonTyp, searchParameters.getWahlvorschlagsPersonTypen());
    }

    queryDsl.eqIfNotNull(wahlvorschlagsPerson.personStatus, searchParameters.getPersonStatus());
    queryDsl.likeNormalizedIfNotBlank(wahlvorschlagsPerson.familiennameNormalisiert, searchParameters.getFamilienname());
    queryDsl.likeInAnyNormalizedIfNotBlank(wahlvorschlagsPerson.rufnameNormalisiert, wahlvorschlagsPerson.vornamenNormalisiert, searchParameters.getVornameSeparator(), searchParameters.getVorname());

    List<WahlvorschlagsPerson> result = new ArrayList<>(queryDsl.list(QWahlvorschlagsPerson.wahlvorschlagsPerson));

    if (searchParameters.hasGebiet()) {
      filterByGebiet(result, searchParameters.getGebiet());
    }

    if (searchParameters.hasGruppe()) {
      filterByGruppe(result, searchParameters.getGruppe());
    }

    sort(result, getWahlvorschlagsPersonComparator());

    return result;
  }

  private void filterByGebiet(List<WahlvorschlagsPerson> result, Gebiet gebiet) {
    for (Iterator<WahlvorschlagsPerson> iter = result.iterator(); iter.hasNext();) {
      Gebiet wahlvorschlagsPersonGebiet = iter.next().getGebiet();

      if (!wahlvorschlagsPersonGebiet.equals(gebiet) && !wahlvorschlagsPersonGebiet.isUntergebietVon(gebiet)) {
        iter.remove();
      }
    }
  }

  private void filterByGruppe(List<WahlvorschlagsPerson> result, Gruppe gruppe) {
    for (Iterator<WahlvorschlagsPerson> iter = result.iterator(); iter.hasNext();) {
      if (!gruppe.equals(iter.next().getGruppe())) {
        iter.remove();
      }
    }
  }

  protected Comparator<WahlvorschlagsPerson> getWahlvorschlagsPersonComparator() {
    return DEFAULT_WAHLVORSCHLAGS_PERSON_COMPARATOR;
  }

  private Wahlvorschlag attach(Wahlvorschlag wahlvorschlag) {
    return wahlvorschlagDao.attach(wahlvorschlag);
  }
}
