
package de.ivu.elect.presentation;

import static de.ivu.elect.business.search.control.AbstractElectSearchParameters.GLOBAL_MAX_LIMIT;

import java.util.List;
import java.util.Optional;

import javax.annotation.Nullable;
import javax.inject.Inject;

import org.primefaces.model.menu.MenuModel;

import de.ivu.elect.business.benutzerverwaltung.entity.Benutzer;
import de.ivu.elect.business.benutzerverwaltung.entity.BenutzerEinsatzort;
import de.ivu.elect.business.gebietsbaum.boundary.GebietSessionSelect;
import de.ivu.elect.business.gebietsbaum.entity.EntityMitGebiet;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.mandant.entity.Mandant;
import de.ivu.elect.business.search.boundary.BenutzerSuchparameterExtendedDAO;
import de.ivu.elect.business.search.boundary.BenutzerSuchparameterSearch;
import de.ivu.elect.business.search.control.AbstractElectSearchParameters;
import de.ivu.elect.business.search.entity.BenutzerSuchparameter;
import de.ivu.elect.business.security.UseCasePermission;
import de.ivu.elect.business.security.control.ElectCommonPrincipal;
import de.ivu.elect.business.wahl.boundary.WahlSessionSelector;
import de.ivu.elect.business.wahl.boundary.WahlterminSessionSelect;
import de.ivu.elect.business.wahl.entity.Wahl;
import de.ivu.elect.business.wahl.entity.Wahltermin;
import de.ivu.elect.jee.presentation.AjaxHelper;
import de.ivu.elect.jee.presentation.dynamicform.AdvancedDynaFormModel;
import de.ivu.elect.jee.presentation.dynamicform.DynamicFormSearchView;
import de.ivu.elect.jee.presentation.dynamicform.FormBuilder;
import de.ivu.elect.jee.presentation.dynamicform.Property;
import de.ivu.elect.jee.presentation.redirect.Redirect;
import de.ivu.elect.jee.presentation.search.AbstractPaginationSearchView;
import de.ivu.elect.jee.presentation.security.StartViewResolver;
import de.ivu.elect.presentation.gebietsbaum.GebietNummerFormatter;
import de.ivu.elect.presentation.mandant.MandantSessionSelect;
import de.ivu.elect.presentation.search.menu.BenutzerSuchparameterOptionenMenu;
import de.ivu.elect.presentation.security.AbstractGebietSecurityFilter;
import de.ivu.elect.presentation.security.BearbeitenGebietSecurity;
import de.ivu.elect.presentation.security.EinsehenGebietSecurity;
import de.ivu.elect.presentation.security.GebietSecurity;

public abstract class AbstractElectSearchView<T> extends AbstractPaginationSearchView<T, AbstractElectSearchParameters>
    implements
      ViewHavingSuchparameterPanel,
      ViewHavingSuchparameterSpeicherbar,
      DynamicFormSearchView {

  private static final long serialVersionUID = 1L;

  protected final AbstractGebietSecurityFilter<T> KEIN_GEBIET_SECURITY_FILTER = AbstractGebietSecurityFilter.<T> createKeinFilter();

  protected final String SEARCH_RESULT_FORM_ID = "frmSearchResult";

  @Inject
  private transient ElectCommonPrincipal principal;

  @Inject
  private transient GebietSecurity gebietSecurity;

  @Inject
  private transient StartViewResolver startViewResolver;

  @Inject
  private transient WahlterminSessionSelect wahlterminSessionSelect;

  @Inject
  private transient WahlSessionSelector wahlSessionSelector;

  @Inject
  private transient GebietSessionSelect gebietSessionSelect;

  @Inject
  private transient GebietNummerFormatter gebietNummerFormatter;

  @Inject
  private transient BenutzerSuchparameterSearch benutzerSuchparameterSearch;

  @Inject
  private transient BenutzerSuchparameterExtendedDAO benutzerSuchparameterExtendedDAO;

  @Inject
  private transient BenutzerSuchparameterOptionenMenu benutzerSuchparameterOptionenMenu;

  @Inject
  private transient MandantSessionSelect mandantSessionSelect;

  private BenutzerSuchparameter benutzerSuchparameterInitialisiert;

  private AdvancedDynaFormModel searchParametersModel;

  private boolean redirectOnUniqueSearchResultEnabled;

  protected boolean searchResultLimitExceeded;

  public AbstractElectSearchView() {
    super();
  }

  protected ElectCommonPrincipal getPrincipal() {
    return principal;
  }

  protected Benutzer getSelectedBenutzer() {
    return getPrincipal().getLogin();
  }

  protected String getSelectedBenutzerKennung() {
    return getPrincipal().getBenutzerKennung();
  }

  protected BenutzerEinsatzort getSelectedBenutzerEinsatzort() {
    return getPrincipal().getBenutzerEinsatzort();
  }

  protected String getSelectedBenutzerNameFormatted() {
    return getPrincipal().getFormattedName();
  }

  protected boolean hasPermission(UseCasePermission permission) {
    return getPrincipal().hasUseCasePermission(permission);
  }

  protected BearbeitenGebietSecurity getBearbeitenGebietSecurity() {
    return gebietSecurity.getBearbeitenGebietSecurity();
  }

  protected EinsehenGebietSecurity getEinsehenGebietSecurity() {
    return gebietSecurity.getEinsehenGebietSecurity();
  }

  protected String getStartViewId() {
    return startViewResolver.getStartViewId();
  }

  protected boolean isWahlterminSelected() {
    return wahlterminSessionSelect.isSelected();
  }

  protected Wahltermin getSelectedWahltermin() {
    return wahlterminSessionSelect.getEntity();
  }

  protected boolean isWahlSelected() {
    return wahlSessionSelector.isSelected();
  }

  protected Wahl getSelectedWahl() {
    return wahlSessionSelector.getEntity();
  }

  public boolean isGebietSelected() {
    return gebietSessionSelect.isSelected();
  }

  protected Gebiet getSelectedGebiet() {
    return gebietSessionSelect.getEntity();
  }

  protected String getSelectedGebietNameFormatted() {
    return formatGebietName(getSelectedGebiet());
  }

  protected String formatGebietName(Gebiet gebiet) {
    return gebietNummerFormatter.getDarstellung(gebiet);
  }

  @Override
  protected List<T> runSearch() {
    initSearchParametersIfNotInitialized();
    initSearchParametersByContext(); // NOTE ELECT-7671, den Context immer aktuell halten...

    searchResultLimitExceeded = false; // ELECT-8222

    Long searchResultLimit = getSearchResultLimit();
    boolean searchResultLimitConfigured = searchResultLimit != null;

    if (searchResultLimitConfigured && searchResultLimit < Integer.MAX_VALUE) {
      // Inkrementieren, um entscheiden zu können, ob mehr Treffer existieren und ein Hinweis angezeigt werden muss,
      // cf. AbstractElectSearchView#onSearchResultLoaded!
      setSearchResultLimit(searchResultLimit + 1);
    }

    List<T> searchResult = filterGebiete(performSearch());

    if (searchResultLimitConfigured) {
      setSearchResultLimit(searchResultLimit); // Zurücksetzen

      searchResultLimitExceeded = searchResult.size() > searchResultLimit;

      if (searchResultLimitExceeded) {
        searchResult.remove(searchResult.size() - 1);
      }
    }

    return searchResult;
  }

  @Nullable
  protected Long getSearchResultLimit() {
    if (hasSearchParameters()) {
      return getSearchParameters().getLimit();
    }

    return null;
  }

  protected void setSearchResultLimit(Long limit) {
    if (hasSearchParameters()) {
      getSearchParameters().setLimit(limit);
    }
  }

  protected List<T> filterGebiete(List<T> ts) {
    return getGebietSecurityFilter().filter(ts);
  }

  protected abstract List<T> performSearch();

  public boolean isBearbeitenRechtAufAktuellemGebietVorhanden() {
    return getBearbeitenGebietSecurity().isRechtVorhanden(getSelectedGebiet());
  }

  protected boolean isBearbeitenRechtAufGebietVorhanden(EntityMitGebiet entity) {
    return getBearbeitenGebietSecurity().isRechtVorhanden(entity);
  }

  // ELECT-3505 - Wahl(art)- und gebietsabhängiges Rechtekonzept umsetzen
  protected abstract AbstractGebietSecurityFilter<T> getGebietSecurityFilter();

  @Override
  public boolean isSupportsLazyLoading() {
    return true;
  }

  @Override
  public boolean isOptionMenuRendered() {
    return getOptionMenu() != null;
  }

  @Nullable
  @Override
  public MenuModel getOptionMenu() {
    return benutzerSuchparameterOptionenMenu.getMenuModel();
  }

  @Override
  public void initSuchparameterState(BenutzerSuchparameter benutzerSuchparameter) {
    if (benutzerSuchparameter != null) {
      reset();

      presetSuchparameterState(benutzerSuchparameter, "benutzer.suchvorlage.auswaehlen.action.success.message");
    }
  }

  protected void presetSuchparameterState(BenutzerSuchparameter template, String msgKey) {
    BenutzerSuchparameter benutzerSuchparameter = benutzerSuchparameterExtendedDAO.attach(template);

    if (benutzerSuchparameter != null) {
      benutzerSuchparameterInitialisiert = benutzerSuchparameter;

      setSuchparameterState(benutzerSuchparameterInitialisiert.getSuchparameter());

      addInfoMessage(msgKey, benutzerSuchparameterInitialisiert.getName());
    }
  }

  @Override
  public void setSuchparameterState(String suchparameterState) {
    getSearchParameters().setSearchParameterState(suchparameterState);
  }

  @Override
  public String getSuchparameterState() {
    return getSearchParameters().getSearchParameterState();
  }

  @Override
  @Redirect
  public String search() {
    // Methode 'search' wird per Button über der Oberfäche aufgerufen!
    enableRedirectOnUniqueSearchResult();

    initSearchParametersIfNotInitialized();

    return super.search();
  }

  @Override
  public void onSearchResultLoaded() {
    super.onSearchResultLoaded();

    List<T> searchResult = getSearchResult();

    if (searchResultLimitExceeded) { // Cf. AbstractElectSearchView#runSearch
      addInfoMessage("search.filter.max.anzahl.treffer.exceeded.message");
    }

    if (isRedirectOnUniqueSearchResultEnabled()) {
      // Beim Neuladen/Zurückkehren auf die Seite soll nicht wieder automatisch umgeleitet werden!
      disableRedirectOnUniqueSearchResult();

      if (searchResult.size() == 1) {
        T entity = searchResult.get(0);

        onUniqueSearchResultLoaded(entity);
      }
    }
  }

  private void enableRedirectOnUniqueSearchResult() {
    redirectOnUniqueSearchResultEnabled = true;
  }

  private void disableRedirectOnUniqueSearchResult() {
    redirectOnUniqueSearchResultEnabled = false;
  }

  private boolean isRedirectOnUniqueSearchResultEnabled() {
    return redirectOnUniqueSearchResultEnabled;
  }

  protected void onUniqueSearchResultLoaded(T entity) {
    // To be overridden...
  }

  @Override
  public boolean isSearchParametersRendered() {
    return super.isSearchParametersRendered() && getSearchFormModel() != null && getSearchFormModel().isAnyPropertyAdded();
  }

  @Override
  protected void initSearchParametersByDefaults() {
    super.initSearchParametersByDefaults();

    presetSearchParameters();
  }

  /**
   * @see AbstractSearchView#initSearchParametersByDefaults
   * @see AbstractSearchView#initSearchParametersByPrincipal
   * @see AbstractSearchView#initSearchParametersByContext
   * @deprecated NOTE ELECT-7630, dass, um bspw. das Vorbelegen anhand gespeicherter Standardsuchvorlagen zu ermöglichen, beim Vorbelegen der Suchparameter genauer unterschieden werden muss.
   */
  @Deprecated
  protected abstract void presetSearchParameters();

  @Override
  protected void initSearchParametersByPrincipal() {
    super.initSearchParametersByPrincipal();

    if (isSuchparameterStateAlsDefaultSupported()) {
      Optional<BenutzerSuchparameter> benutzerSuchparameterAlsDefault = Optional.empty();

      if (isWahlSelected()) {
        benutzerSuchparameterAlsDefault = findBenutzerSuchparameterAlsDefault(getSelectedWahl());
      } else if (isWahlterminSelected()) {
        benutzerSuchparameterAlsDefault = findBenutzerSuchparameterAlsDefault(getSelectedWahltermin());
      }

      benutzerSuchparameterAlsDefault.ifPresent(benutzerSuchparameter -> presetSuchparameterState(benutzerSuchparameter, "benutzer.suchvorlage.auswaehlen.als.default.action.success.message"));
    }
  }

  @Override
  public boolean isSuchparameterStateAlsDefaultSupported() {
    return false; // ELECT-7630
  }

  @Override
  public AdvancedDynaFormModel getSearchFormModel() {
    if (searchParametersModel == null) {
      searchParametersModel = createSearchParametersModel();
    }

    return searchParametersModel;
  }

  @Override
  public void resetSearchParametersModel() {
    searchParametersModel = null;
  }

  private AdvancedDynaFormModel createSearchParametersModel() {
    initSearchParametersIfNotInitialized();

    FormBuilder formBuilder = new FormBuilder();
    addProperties(formBuilder);

    return formBuilder.createModel();
  }

  protected void addLimitProperty(FormBuilder formBuilder) {
    addLimitProperty(formBuilder, getSearchParameters().getLimitProperty(GLOBAL_MAX_LIMIT));
  }

  protected void addLimitProperty(FormBuilder formBuilder, Property limitProperty) {
    formBuilder.addSeparator("limit.separator");
    formBuilder.addProperty(limitProperty);
  }

  @Override
  @Redirect
  public String reset() {
    resetSearchParametersModel();
    resetSearchResult(); // Suchergebnis zurücksetzen, weil sonst beim Wechseln der(s) Wahl(termins) nicht das Ergebnis gelöscht wird

    return super.reset();
  }

  @Override // => public
  public void resetSearchResult() {
    super.resetSearchResult();
  }

  private Optional<BenutzerSuchparameter> findBenutzerSuchparameterAlsDefault(Wahl wahl) {
    return findBenutzerSuchparameterAlsDefault(null, wahl);
  }

  private Optional<BenutzerSuchparameter> findBenutzerSuchparameterAlsDefault(Wahltermin wahltermin) {
    return findBenutzerSuchparameterAlsDefault(wahltermin, null);
  }

  private Optional<BenutzerSuchparameter> findBenutzerSuchparameterAlsDefault(Wahltermin wahltermin, Wahl wahl) {
    return benutzerSuchparameterSearch.findBenutzerSuchparameterAlsDefault(wahltermin, wahl, getSelectedBenutzerKennung(), getViewId());
  }

  protected Mandant getMandant() {
    return mandantSessionSelect.getMandant();
  }

  protected Mandant bestimmeMandantAusWahlWahlterminMandant() {
    Wahl selectedWahl = getSelectedWahl();
    if (selectedWahl != null) {
      return selectedWahl.getMandant();
    }

    Wahltermin selectedWahltermin = getSelectedWahltermin();
    if (selectedWahltermin != null) {
      return selectedWahltermin.getMandant();
    }

    return getMandant();
  }

  /**
   * HINT: Aufzurufen während eines AJAX-Requests (aka "Partial Request"), z.B. in einem Event-Observer. Nützlich z.B. beim Ändern von Entities aus einem Action-Menü heraus.
   * <p/>
   * NOTE: Zusätzlich muss am jeweiligen ColumnBuilder forceInit() aufgerufen werden, ansonsten erhalten wir durch cecachte Spalten eine LazyInitializationException.
   * <p/>
   * NOTE: Zusätzlich kann am jeweiligen ActionMenu resetMenuItemsPerEntity() aufgerufen werden, damit Änderungen im Menü (wie z.B. Ein/Ausblenden von Menüeinträgen) sichtbar werden.
   */
  protected void reloadSearchResultDuringPartialRequest() {
    setSearchReady();
    ajaxHelper.update(SEARCH_RESULT_FORM_ID);
  }

  protected void reloadMessagesDuringPartialRequest() {
    ajaxHelper.update(AjaxHelper.GLOBAL_MESSAGES_ID);
  }
}
