
package de.ivu.elect.presentation.wahlvorschlag;

import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.function.Supplier;

import javax.annotation.PostConstruct;
import javax.enterprise.context.RequestScoped;
import javax.enterprise.event.Observes;
import javax.enterprise.event.Reception;
import javax.enterprise.inject.Instance;
import javax.faces.context.FacesContext;
import javax.inject.Inject;
import javax.inject.Named;

import org.apache.deltaspike.core.api.scope.ConversationGroup;
import org.apache.deltaspike.core.api.scope.ViewAccessScoped;

import de.ivu.elect.business.gebietsbaum.boundary.GebietSelectChangeEvent;
import de.ivu.elect.business.gebietsbaum.entity.EntityMitGebiet;
import de.ivu.elect.business.gebietsbaum.entity.Gebiet;
import de.ivu.elect.business.gruppe.entity.ReferendumVorlageConstants;
import de.ivu.elect.business.gruppeimgebiet.boundary.GruppeImGebietSearch;
import de.ivu.elect.business.gruppeimgebiet.boundary.ReferendumVorlageImGebietSearchParameters;
import de.ivu.elect.business.kandidatur.boundary.AbstractDirektkandidaturSearchParameters;
import de.ivu.elect.business.kandidatur.boundary.DirektkandidaturChangeEvent;
import de.ivu.elect.business.kandidatur.boundary.DirektkandidaturSearch;
import de.ivu.elect.business.kandidatur.boundary.DirektkandidaturSearchParameters;
import de.ivu.elect.business.kandidatur.entity.DirektkandidaturConstants;
import de.ivu.elect.business.security.UseCasePermission;
import de.ivu.elect.business.security.UseCaseSecure;
import de.ivu.elect.business.wahl.boundary.WahlSelectChangeEvent;
import de.ivu.elect.business.wahlvorschlag.boundary.AbstractListeSearchParameters;
import de.ivu.elect.business.wahlvorschlag.boundary.AbstractWahlvorschlagSearchParameters;
import de.ivu.elect.business.wahlvorschlag.boundary.ListeChangeEvent;
import de.ivu.elect.business.wahlvorschlag.boundary.ListeSearch;
import de.ivu.elect.business.wahlvorschlag.boundary.ListeSearchParameters;
import de.ivu.elect.business.wahlvorschlag.boundary.WahlvorschlagSearchParameters;
import de.ivu.elect.business.wahlvorschlag.boundary.WahlvorschlagZulassungsvorschlagSetzer;
import de.ivu.elect.business.wahlvorschlag.entity.ListeConstants;
import de.ivu.elect.business.wahlvorschlag.entity.WahlvorschlagConstants;
import de.ivu.elect.business.wahlvorschlag.entity.WahlvorschlagStatus;
import de.ivu.elect.business.wahlvorschlag.entity.ZulassungsvorschlagSetzerErgebnis;
import de.ivu.elect.jee.entity.EagerLoadingCollector;
import de.ivu.elect.jee.faces.PreConditionCheck;
import de.ivu.elect.jee.presentation.dynamicform.FormBuilder;
import de.ivu.elect.jee.presentation.dynamictable.AbstractColumnBuilder;
import de.ivu.elect.jee.presentation.redirect.Redirect;
import de.ivu.elect.presentation.AbstractElectSearchView;
import de.ivu.elect.presentation.View;
import de.ivu.elect.presentation.gruppeimgebiet.table.ReferendumVorlageImGebietColumnBuilder;
import de.ivu.elect.presentation.hilfe.OnlineHilfeId;
import de.ivu.elect.presentation.kandidatur.table.DirektkandidaturColumnBuilder;
import de.ivu.elect.presentation.security.AbstractGebietSecurityFilter;
import de.ivu.elect.presentation.wahlvorschlag.menu.WahlvorschlagSearchGoToUebergeordnetesDirektwahlgebietMenuItemEntry;
import de.ivu.elect.presentation.wahlvorschlag.menu.WahlvorschlagSearchGoToUebergeordnetesListenwahlgebietMenuItemEntry;
import de.ivu.elect.presentation.wahlvorschlag.menu.WahlvorschlagSearchGoToUebergeordnetesReferendumgebietMenuItemEntry;
import de.ivu.elect.presentation.wahlvorschlag.table.ListeColumnBuilder;
import de.ivu.elect.presentation.wahlvorschlag.table.WahlvorschlagColumnBuilder;

@OnlineHilfeId(value = "443", mehrfachVergebenOk = true)
@ViewAccessScoped
@Named(WahlvorschlagSearchView.COMPONENT_NAME)
@View(viewId = WahlvorschlagSearchView.VIEW_ID, useCase = UseCasePermission.OHNE)
public class WahlvorschlagSearchView extends AbstractElectSearchView<EntityMitGebiet> implements WahlvorschlagSearchViewResolverTarget {

  private static final long serialVersionUID = 1L;

  public static final String COMPONENT_NAME = "wahlvorschlagSearchView";

  public static final String VIEW_ID = "/wahlvorschlag/wahlvorschlag-search.xhtml";

  @Inject
  private transient RequestHandlerWahlvorschlagSearchView requestHandler;

  @Inject
  @ConversationGroup(AbstractWahlvorschlagSearchParameters.class)
  private transient WahlvorschlagSearchParameters searchParameters;

  @Inject
  private transient WahlvorschlagColumnBuilder wahlvorschlagColumnBuilder;

  @Inject
  private transient WahlvorschlagSearchGoToUebergeordnetesListenwahlgebietMenuItemEntry wahlvorschlagSearchGoToUebergeordnetesListenwahlgebietMenuItemEntry;

  @Inject
  private transient WahlvorschlagSearchGoToUebergeordnetesDirektwahlgebietMenuItemEntry wahlvorschlagSearchGoToUebergeordnetesDirektwahlgebietMenuItemEntry;

  @Inject
  private transient WahlvorschlagSearchGoToUebergeordnetesReferendumgebietMenuItemEntry wahlvorschlagSearchGoToUebergeordnetesReferendumgebietMenuItemEntry;

  @Inject
  private transient WahlvorschlagSearchViewResolver wahlvorschlagSearchViewResolver;

  @Inject
  private transient WahlvorschlagZulassungsvorschlagSetzer wahlvorschlagZulassungsvorschlagSetzer;

  @Inject
  private transient ListeSearch listenwahlgebietSearch;

  @Inject
  @ConversationGroup(AbstractListeSearchParameters.class)
  private transient ListeSearchParameters listenwahlgebietSearchParameters;

  @Inject
  private transient ListeColumnBuilder listenwahlgebietColumnBuilder;

  @Inject
  private transient DirektkandidaturSearch direktwahlgebietSearch;

  @Inject
  @ConversationGroup(AbstractDirektkandidaturSearchParameters.class)
  private transient DirektkandidaturSearchParameters direktwahlgebietSearchParameters;

  @Inject
  private transient DirektkandidaturColumnBuilder direktwahlgebietColumnBuilder;

  @Inject
  private transient GruppeImGebietSearch referendumgebietSearch;

  @Inject
  @ConversationGroup(ReferendumVorlageImGebietSearchParameters.class)
  private transient ReferendumVorlageImGebietSearchParameters referendumgebietSearchParameters;

  @Inject
  private transient ReferendumVorlageImGebietColumnBuilder referendumgebietColumnBuilder;

  public WahlvorschlagSearchView() {
    super();
  }

  @PreConditionCheck
  public String checkCondition() {
    return wahlvorschlagSearchViewResolver.checkCondition(VIEW_ID);
  }

  @Override
  public void preRenderView() {
    if (isSearchResultUebergeordnetesWahlgebietRendered()) {
      for (Entry<String, Object[]> entry : collectSearchResultMessages().entrySet()) {
        addInfoMessage(entry.getKey(), entry.getValue());
      }
    }
  }

  @Override
  protected AbstractGebietSecurityFilter<EntityMitGebiet> getGebietSecurityFilter() {
    return AbstractGebietSecurityFilter.createEntityMitGebietSecurityFilter(getEinsehenGebietSecurity());
  }

  @Override
  protected String getViewId() {
    return VIEW_ID;
  }

  public String getPageTitle() {
    return getMessage("entity.search.page.title", getMessage(getEntityMehrzahlMsgKey()));
  }

  public String getPageHeader() {
    return getMessage("entity.mit.gebiet.search.page.header", getMessage(getEntityMehrzahlMsgKey()), getGebietName());
  }

  private String getEntityMehrzahlMsgKey() {
    return invokeIfSearchResultWahlgebietRendered( //
        () -> ListeConstants.MESSAGE_KEY_ENTITY_MEHRZAHL, //
        () -> DirektkandidaturConstants.MESSAGE_KEY_ENTITY_MEHRZAHL, //
        () -> ReferendumVorlageConstants.MESSAGE_KEY_ENTITY_MEHRZAHL, //
        () -> WahlvorschlagConstants.MESSAGE_KEY_ENTITY_MEHRZAHL //
    );
  }

  @Override
  public String getGebietName() {
    return getSelectedGebietNameFormatted();
  }

  public List<String> getSearchResultMessages() {
    List<String> result = new ArrayList<>();

    if (!isSearchResultUebergeordnetesWahlgebietRendered()) {
      for (Entry<String, Object[]> entry : collectSearchResultMessages().entrySet()) {
        result.add(getMessage(entry.getKey(), entry.getValue()));
      }
    }

    return result;
  }

  private LinkedHashMap<String, Object[]> collectSearchResultMessages() {
    LinkedHashMap<String, Object[]> result = new LinkedHashMap<>();

    if (isWahlMitReferendengebieten()) {
      String mgsKey = "wahlvorschlag.search.message.uebergeordnetes.referendumgebiet.exists";

      if (existsUebergeordnetesReferendumgebiet()) {
        result.put(mgsKey, new Object[] {formatGebietName(getUebergeordnetesReferendumgebiet())});
      } else {
        result.put(mgsKey + ".not", new Object[] {});
      }
    } else {
      boolean uebergeordnetesListenwahlgebietExists = existsUebergeordnetesListenwahlgebiet();
      boolean uebergeordnetesDirektwahlgebietExists = existsUebergeordnetesDirektwahlgebiet();

      if (uebergeordnetesListenwahlgebietExists || uebergeordnetesDirektwahlgebietExists) {
        Gebiet uebergeordnetesListenwahlgebiet = getUebergeordnetesListenwahlgebiet();
        Gebiet uebergeordnetesDirektwahlgebiet = getUebergeordnetesDirektwahlgebiet();

        if (uebergeordnetesListenwahlgebietExists && uebergeordnetesDirektwahlgebietExists) {
          String msgKeyPrefix = "wahlvorschlag.search.message.uebergeordnetes.listenwahlgebiet.direktwahlgebiet.";

          if (Objects.equals(uebergeordnetesListenwahlgebiet, uebergeordnetesDirektwahlgebiet)) {
            result.put(msgKeyPrefix + "gemeinsam.exists", new Object[] {formatGebietName(uebergeordnetesListenwahlgebiet)});
          } else {
            result.put(msgKeyPrefix + "einzeln.exists", new Object[] {formatGebietName(uebergeordnetesListenwahlgebiet), formatGebietName(uebergeordnetesDirektwahlgebiet)});
          }
        } else {
          if (uebergeordnetesListenwahlgebietExists) {
            result.put("wahlvorschlag.search.message.uebergeordnetes.listenwahlgebiet.exists", new Object[] {formatGebietName(uebergeordnetesListenwahlgebiet)});
          }

          if (uebergeordnetesDirektwahlgebietExists) {
            result.put("wahlvorschlag.search.message.uebergeordnetes.direktwahlgebiet.exists", new Object[] {formatGebietName(uebergeordnetesDirektwahlgebiet)});
          }
        }
      } else {
        result.put("wahlvorschlag.search.message.untergeordnetes.wahlgebiet.exists", new Object[] {});
      }
    }

    return result;
  }

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

    searchParameters.setGebiet(getSelectedGebiet());

    listenwahlgebietSearchParameters.setGebiet(getUebergeordnetesListenwahlgebiet());
    direktwahlgebietSearchParameters.setGebiet(getUebergeordnetesDirektwahlgebiet());
    referendumgebietSearchParameters.setGebiet(getUebergeordnetesReferendumgebiet());
  }

  /**
   * @deprecated To be removed...
   * @see ReferendumVorlageImGebietSearchView#initSearchParametersByContext
   */
  @Deprecated
  @Override
  public void presetSearchParameters() {
    // To be removed...
  }

  @Override
  public void addProperties(FormBuilder formBuilder) {
    // Do nothing...
  }

  @Override
  public WahlvorschlagSearchParameters getSearchParameters() {
    return searchParameters;
  }

  @Override
  public List<EntityMitGebiet> getSearchResult() {
    requestHandler.nothing(); // ELECT-248

    return super.getSearchResult();
  }

  @Override
  protected List<EntityMitGebiet> performSearch() {
    return new ArrayList<>(invokeIfSearchResultWahlgebietRendered( //
        () -> listenwahlgebietSearch.getSearchResult(listenwahlgebietSearchParameters, createEagerLoadingCollector(listenwahlgebietColumnBuilder)), //
        () -> direktwahlgebietSearch.getSearchResult(direktwahlgebietSearchParameters, createEagerLoadingCollector(direktwahlgebietColumnBuilder)), //
        () -> referendumgebietSearch.getSearchResult(referendumgebietSearchParameters, createEagerLoadingCollector(referendumgebietColumnBuilder)), //
        Collections::emptyList //
    ));
  }

  public AbstractColumnBuilder< ? > getColumnBuilder() {
    return invokeIfSearchResultWahlgebietRendered( //
        () -> listenwahlgebietColumnBuilder, //
        () -> direktwahlgebietColumnBuilder, //
        () -> referendumgebietColumnBuilder, //
        () -> wahlvorschlagColumnBuilder //
    );
  }

  private <R> R invokeIfSearchResultWahlgebietRendered(Supplier<R> invokeIfSearchResultUebergeordnetesListenwahlgebietRendered, Supplier<R> invokeIfSearchResultUebergeordnetesDirektwahlgebietRendered,
      Supplier<R> invokeIfSearchResultUebergeordnetesReferendumgebietRendered, Supplier<R> invokeIfSearchResultEmptyRendered) {
    if (isSearchResultUebergeordnetesListenwahlgebietRendered()) {
      return invokeIfSearchResultUebergeordnetesListenwahlgebietRendered.get();
    } else if (isSearchResultUebergeordnetesDirektwahlgebietRendered()) {
      return invokeIfSearchResultUebergeordnetesDirektwahlgebietRendered.get();
    } else if (isSearchResultUebergeordnetesReferendumgebietRendered()) {
      return invokeIfSearchResultUebergeordnetesReferendumgebietRendered.get();
    }

    return invokeIfSearchResultEmptyRendered.get();
  }

  public boolean isSearchResultUebergeordnetesWahlgebietRendered() {
    return isSearchResultUebergeordnetesListenwahlgebietRendered() || isSearchResultUebergeordnetesDirektwahlgebietRendered() || isSearchResultUebergeordnetesReferendumgebietRendered();
  }

  private boolean isSearchResultUebergeordnetesListenwahlgebietRendered() {
    return wahlvorschlagSearchGoToUebergeordnetesListenwahlgebietMenuItemEntry.isRendered();
  }

  private boolean existsUebergeordnetesListenwahlgebiet() {
    return getUebergeordnetesListenwahlgebiet() != null;
  }

  private Gebiet getUebergeordnetesListenwahlgebiet() {
    return wahlvorschlagSearchGoToUebergeordnetesListenwahlgebietMenuItemEntry.getUebergeordnetesWahlgebiet();
  }

  private boolean isSearchResultUebergeordnetesDirektwahlgebietRendered() {
    return wahlvorschlagSearchGoToUebergeordnetesDirektwahlgebietMenuItemEntry.isRendered();
  }

  private boolean existsUebergeordnetesDirektwahlgebiet() {
    return getUebergeordnetesDirektwahlgebiet() != null;
  }

  private Gebiet getUebergeordnetesDirektwahlgebiet() {
    return wahlvorschlagSearchGoToUebergeordnetesDirektwahlgebietMenuItemEntry.getUebergeordnetesWahlgebiet();
  }

  private boolean isSearchResultUebergeordnetesReferendumgebietRendered() {
    return wahlvorschlagSearchGoToUebergeordnetesReferendumgebietMenuItemEntry.isRendered();
  }

  private boolean existsUebergeordnetesReferendumgebiet() {
    return getUebergeordnetesReferendumgebiet() != null;
  }

  private Gebiet getUebergeordnetesReferendumgebiet() {
    return wahlvorschlagSearchGoToUebergeordnetesReferendumgebietMenuItemEntry.getUebergeordnetesWahlgebiet();
  }

  private EagerLoadingCollector createEagerLoadingCollector(AbstractColumnBuilder< ? > columnBuilder) {
    EagerLoadingCollector eagerLoadingCollector = EagerLoadingCollector.<EntityMitGebiet> create();
    columnBuilder.collectEagerLoadings(eagerLoadingCollector);

    return eagerLoadingCollector;
  }

  @Override
  public String getWahlvorschlagStatusOffenMessage() {
    return getMessage(WahlvorschlagStatus.OFFEN);
  }

  @Override
  @Redirect
  @UseCaseSecure(UseCasePermission.WAHLVORSCHLAG_QS_FUER_ALLE_ZULASSUNGSVORSCHLAG_SETZEN)
  public String fuerAlleZulassungsvorschlagSetzen() {
    ZulassungsvorschlagSetzerErgebnis result = wahlvorschlagZulassungsvorschlagSetzer.fuerAlleZulassungsvorschlagSetzen(getSelectedGebiet());

    String msgKeyWahlvorschlagZugelassen = "wahlvorschlag.search.action.fuer.alle.zulassungsvorschlag.setzen.success.message.wahlvorschlag.zugelassen.plural";

    if (result.getWahlvorschlaegeZugelassen() == 1) {
      msgKeyWahlvorschlagZugelassen = "wahlvorschlag.search.action.fuer.alle.zulassungsvorschlag.setzen.success.message.wahlvorschlag.zugelassen.singular";
    }

    addInfoMessage(msgKeyWahlvorschlagZugelassen, result.getWahlvorschlaegeZugelassen());

    if (isWahlMitListengebieten()) {
      String msgKeyListenkandidaturZugelassen = "wahlvorschlag.search.action.fuer.alle.zulassungsvorschlag.setzen.success.message.listenkandidatur.zugelassen.plural";

      if (result.getListenkandidaturenZugelassen() == 1) {
        msgKeyListenkandidaturZugelassen = "wahlvorschlag.search.action.fuer.alle.zulassungsvorschlag.setzen.success.message.listenkandidatur.zugelassen.singular";
      }

      addInfoMessage(msgKeyListenkandidaturZugelassen, result.getListenkandidaturenZugelassen());
    }

    return VIEW_ID;
  }

  private boolean isWahlMitListengebieten() {
    return isGebietSelected() && getSelectedGebiet().getWahl().isWahlMitListengebieten();
  }

  private boolean isWahlMitReferendengebieten() {
    return isGebietSelected() && getSelectedGebiet().getWahl().isWahlMitReferendengebieten();
  }

  public void onWahlSelectChangeEvent(@Observes(notifyObserver = Reception.IF_EXISTS) WahlSelectChangeEvent event) {
    reset();
  }

  public void onGebietSelectChangeEvent(@Observes(notifyObserver = Reception.IF_EXISTS) GebietSelectChangeEvent event) {
    reset();
  }

  public void onListeChangeEvent(@Observes(notifyObserver = Reception.IF_EXISTS) ListeChangeEvent event) {
    reset();
  }

  public void onDirektkandidaturChangeEvent(@Observes(notifyObserver = Reception.IF_EXISTS) DirektkandidaturChangeEvent event) {
    reset();
  }

  @RequestScoped //  Das Suchergebnis soll nur einmal pro Request geladen werden, daher gibt es dieses "Konstrukt" aus "SearchView" und "RequestHandlerAbstract"
  public static class RequestHandlerWahlvorschlagSearchView {

    @View
    @Inject
    private transient Instance<WahlvorschlagSearchView> searchViews;

    @PostConstruct
    public void init() {
      // ELECT-3543 Doppelte Suchen vermeiden - Suchergebnis für Request nicht beim POST-löschen
      if (!FacesContext.getCurrentInstance().isPostback()) {
        for (WahlvorschlagSearchView searchView : searchViews) {
          searchView.resetSearchResult();
        }
      }
    }

    public void nothing() {
      // Do nothing...
    }
  }
}
