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

import static java.util.Arrays.stream;
import static java.util.stream.Collectors.toSet;
import static org.apache.commons.lang3.StringUtils.isNotBlank;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;

import javax.inject.Inject;

import de.ivu.elect.business.person.boundary.SearchParametersMitOrdnungsmerkmal;
import de.ivu.elect.business.person.boundary.SearchParametersMitPersonendaten;
import de.ivu.elect.business.person.entity.Geschlecht;
import de.ivu.elect.business.person.entity.PersonStatus;
import de.ivu.elect.business.wahl.boundary.WahlExtendedDAO;
import de.ivu.elect.business.wahl.entity.Wahl;
import de.ivu.elect.business.wahlvorschlagsperson.control.WahlvorschlagsPersonPoolSearchResultEntryByTemplateComparator;
import de.ivu.elect.business.wahlvorschlagsperson.control.WahlvorschlagsPersonTypResolver;
import de.ivu.elect.business.wahlvorschlagsperson.entity.QWahlvorschlagsPerson;
import de.ivu.elect.business.wahlvorschlagsperson.entity.WahlvorschlagsPerson;
import de.ivu.elect.business.wahlvorschlagsperson.entity.WahlvorschlagsPersonPoolSearchResultEntry;
import de.ivu.elect.business.wahlvorschlagsperson.entity.WahlvorschlagsPersonTyp;
import de.ivu.elect.jee.entity.EagerLoadingCollector;
import de.ivu.elect.jee.entity.QueryDsl;

public class WahlvorschlagsPersonPoolSearch implements Serializable {

  private static final long serialVersionUID = 1L;

  private static final Comparator<WahlvorschlagsPersonPoolSearchResultEntry> DEFAULT_COMPARATOR = new WahlvorschlagsPersonPoolSearchResultEntryByTemplateComparator();

  @Inject
  protected transient WahlExtendedDAO wahlDao;

  @Inject
  protected transient WahlvorschlagsPersonExtendedDAO wahlvorschlagsPersonDao;

  @Inject
  protected transient WahlvorschlagsPersonPoolPersonenVerknuepfungResolver wahlvorschlagsPersonPoolPersonenVerknuepfungResolver;

  @Inject
  protected transient WahlvorschlagsPersonTypResolver wahlvorschlagsPersonTypResolver;

  public WahlvorschlagsPersonPoolSearch() {
    super();
  }

  public List<WahlvorschlagsPersonPoolSearchResultEntry> getSearchResult(Wahl wahl, SearchParametersMitPersonendaten searchByPersonendatenSearchParameters,
      SearchParametersMitOrdnungsmerkmal searchByOrdnungsmerkmalSearchParameters, EagerLoadingCollector<WahlvorschlagsPersonPoolSearchResultEntry> eagerLoadingCollector) {
    List<WahlvorschlagsPerson> wahlvorschlagsPersonen = new ArrayList<>();

    if (isNotBlank(searchByOrdnungsmerkmalSearchParameters.getOrdnungsmerkmal())) {
      wahlvorschlagsPersonen.addAll(getWahlvorschlagsPersonenByOrdnungsmerkmal(wahl, searchByOrdnungsmerkmalSearchParameters));
    } else {
      wahlvorschlagsPersonen.addAll(getWahlvorschlagsPersonenByPersonendaten(wahl, searchByPersonendatenSearchParameters));
    }

    return eagerLoadingCollector.eagerLoad(createSearchResult(wahlvorschlagsPersonen));
  }

  protected List<WahlvorschlagsPersonPoolSearchResultEntry> createSearchResult(List<WahlvorschlagsPerson> wahlvorschlagsPersonen) {
    List<WahlvorschlagsPersonPoolSearchResultEntry> result = new ArrayList<>();

    Collection<WahlvorschlagsPerson> wahlvorschlagsPersonenPool = new HashSet<>();

    for (WahlvorschlagsPerson wahlvorschlagsPerson : wahlvorschlagsPersonen) {
      if (!containsAnyWahlvorschlagsPersonVerknuepft(wahlvorschlagsPersonenPool, wahlvorschlagsPerson)) {
        wahlvorschlagsPersonenPool.add(wahlvorschlagsPerson);
      }
    }

    for (WahlvorschlagsPerson wahlvorschlagsPerson : wahlvorschlagsPersonenPool) {
      result.add(new WahlvorschlagsPersonPoolSearchResultEntry(wahlvorschlagsPerson, getWahlvorschlagsPersonenVerknuepft(wahlvorschlagsPerson)));
    }

    Collections.sort(result, getDefaultComparator());

    return result;
  }

  private Comparator<WahlvorschlagsPersonPoolSearchResultEntry> getDefaultComparator() {
    return DEFAULT_COMPARATOR;
  }

  private boolean containsAnyWahlvorschlagsPersonVerknuepft(Collection<WahlvorschlagsPerson> wahlvorschlagsPersonenPool, WahlvorschlagsPerson wahlvorschlagsPerson) {
    Collection<WahlvorschlagsPerson> wahlvorschlagsPersonenVerknuepft = getWahlvorschlagsPersonenVerknuepft(wahlvorschlagsPerson);

    for (WahlvorschlagsPerson wahlvorschlagsPersonVerknuepft : wahlvorschlagsPersonenVerknuepft) {
      if (wahlvorschlagsPersonenPool.contains(wahlvorschlagsPersonVerknuepft)) {
        return true;
      }
    }

    return false;
  }

  private Collection< ? extends WahlvorschlagsPerson> getWahlvorschlagsPersonenByOrdnungsmerkmal(Wahl wahl, SearchParametersMitOrdnungsmerkmal searchParameters) {
    QWahlvorschlagsPerson wahlvorschlagsPerson = QWahlvorschlagsPerson.wahlvorschlagsPerson;

    QueryDsl queryDsl = createDefaultQueryDsl(wahlvorschlagsPerson, wahl);
    queryDsl.eq(wahlvorschlagsPerson.externesOrdnungsmerkmal, searchParameters.getOrdnungsmerkmal());

    return queryDsl.list(wahlvorschlagsPerson);
  }

  private Collection< ? extends WahlvorschlagsPerson> getWahlvorschlagsPersonenByPersonendaten(Wahl wahl, SearchParametersMitPersonendaten searchParameters) {
    QWahlvorschlagsPerson wahlvorschlagsPerson = QWahlvorschlagsPerson.wahlvorschlagsPerson;
    QueryDsl queryDsl = createDefaultQueryDsl(wahlvorschlagsPerson, wahl);

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

    Geschlecht geschlecht = searchParameters.getGeschlecht();

    if (!Geschlecht.UNBEKANNT.equals(geschlecht)) {
      queryDsl.eqIfNotNull(wahlvorschlagsPerson.geschlecht, geschlecht);
    }

    return queryDsl.getQuery().fetch();
  }

  private QueryDsl createDefaultQueryDsl(QWahlvorschlagsPerson wahlvorschlagsPerson, Wahl wahl) {
    QueryDsl queryDsl = wahlvorschlagsPersonDao.getQueryDsl();
    queryDsl.from(wahlvorschlagsPerson);

    Collection<PersonStatus> personStatus = stream(PersonStatus.values()).filter(PersonStatus::isGeeignetOderNichtZuPruefen).collect(toSet()); // ELECT-8462
    Collection<WahlvorschlagsPersonTyp> wahlvorschlagsPersonTypen = getWahlvorschlagsPersonTypen();

    queryDsl.eq(wahlvorschlagsPerson.wahl, wahlDao.attach(wahl));
    queryDsl.eqIn(wahlvorschlagsPerson.personStatus, personStatus);
    queryDsl.eqIn(wahlvorschlagsPerson.wahlvorschlagsPersonTyp, wahlvorschlagsPersonTypen);

    return queryDsl;
  }

  private Collection<WahlvorschlagsPersonTyp> getWahlvorschlagsPersonTypen() {
    return wahlvorschlagsPersonTypResolver.getWahlvorschlagsPersonTypen();
  }

  private Collection<WahlvorschlagsPerson> getWahlvorschlagsPersonenVerknuepft(WahlvorschlagsPerson wahlvorschlagsPerson) {
    return wahlvorschlagsPersonPoolPersonenVerknuepfungResolver.getWahlvorschlagsPersonenVerknuepft(wahlvorschlagsPerson);
  }
}
