
package de.ivu.elect.jee.presentation.action;

import static org.apache.commons.lang3.StringUtils.isEmpty;
import static org.apache.commons.lang3.StringUtils.isNotBlank;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.enterprise.context.RequestScoped;
import javax.faces.event.ActionEvent;
import javax.faces.event.ActionListener;
import javax.inject.Inject;

import org.primefaces.event.SelectEvent;
import org.primefaces.model.menu.MenuElement;

import de.ivu.elect.jee.entity.Entity;
import de.ivu.elect.jee.exception.ElectCommonException;
import de.ivu.elect.jee.exception.ElectCommonExceptionHandler;
import de.ivu.elect.jee.faces.FacesContextUtil;
import de.ivu.elect.jee.faces.Messages;
import de.ivu.elect.jee.presentation.AjaxHelper;
import de.ivu.elect.jee.presentation.DialogActionListener;
import de.ivu.elect.jee.presentation.Icons;
import de.ivu.elect.jee.presentation.menu.IVUDefaultMenuItem;
import de.ivu.elect.util.ClassUtil;

@RequestScoped
public abstract class AbstractBaseActionEntry<E extends Entity< ? , ? >> implements ActionEntry<E>, ActionListener, DialogActionListener {

  private static final long serialVersionUID = 1L;

  @Inject
  protected transient AjaxHelper ajaxHelper;

  @Inject
  protected transient Messages messages;

  @Inject
  protected transient Icons appIcons;

  public AbstractBaseActionEntry() {
    super();
  }

  @Override
  public MenuElement buildComponent(E entity) {
    IVUDefaultMenuItem menuItem = new IVUDefaultMenuItem();

    buildMenuItem(entity, menuItem);

    return menuItem;
  }

  protected void buildMenuItem(E entity, IVUDefaultMenuItem menuItem) {
    menuItem.setId(getSimpleClassName()); // ID ist wichtig! ELECT-2453

    // HINT Es müssen leider immer alle Actions (einer Art, z.B. BenutzerEditAction) in einer Tabelle gleichzeitig aktiviert oder deaktiviert sein, unabhängig vom Zustand der Zeile. (Bug in Primefaces)
    menuItem.setRendered(hasPrincipalRequiredPermission());

    // NOTE, dass dieser Pseudo-Style erwartet wird, um eine Action typsicher per CSS-Rule oder XPath finden zu können!
    String pseudoStyleClass = getSimpleClassName(getClass());

    menuItem.setStyleClass(pseudoStyleClass);

    if (isDisabled(entity)) {
      // NOTE, dass der Menü-Eintrag nur ausgeblendet werden darf und ins Menü geschrieben werden MUSS, damit dieses funktioniert!
      menuItem.setDisabled(true);
      menuItem.setStyleClass(menuItem.getStyleClass() + " " + "ui-menuitem-disabled");
    }

    menuItem.setAjax(isAsyncSupported());

    if (isBlockUIAnzeigen(entity)) {
      menuItem.setOnstart(ajaxHelper.createOpenGlobalBlockUiStatement());

      // BlockUI wird nach der Bearbeitung des Ereignisses eventuell entfernt.
      //
    }

    menuItem.setValue(getLocalizedLabel());
    menuItem.setIcon(getIcon());

    if (isShowDialog()) {
      menuItem.setUpdate(":" + getDialogFormId());
    }

    menuItem.addActionListener(this);
  }

  protected String getLocalizedLabel() {
    return getMessage(getLabelKey());
  }

  protected boolean isBlockUIAnzeigen(E entity) {
    // Nur bei asynchronen Operationen (nicht bei Dateidownload) und wenn kein Dialog aufgeht anzeigen
    return isAsyncSupported() && !isShowDialog() && isEmpty(getExternalUrl(entity));
  }

  /**
   * Überschreiben, wenn eine externe Url geöffnet werden soll
   */
  @Nullable
  public String getExternalUrl(E entity) {
    return null;
  }

  @Override
  public void processAction(ActionEvent event) {
    executeAction(event.getComponent().getParent().getAttributes().get("entity"));
  }

  public void onRowDblselect(SelectEvent event) {
    try {
      E entity = (E) event.getObject();

      // NOTE, dass der Dblselect-Listener an einer Tabelle, aber nicht an einer einzelnen Zeile registriert wird. Es ist also nicht
      // möglich diesen Listener bedingt für eine einzelne (bspw. gesperrte) Zeile zu deaktivieren. Deshalb wird hier initial nochmals
      // die Zulässigkeit der Aktion geprüft!
      if (isActionPossibleOnRowDblselect(entity)) {
        executeAction(entity);
      }
    } catch (Exception e) {
      ElectCommonExceptionHandler.handleException(e, messages);
    }
  }

  @Nullable
  @Override
  public String action(E entity) {
    String result;

    if (isActionPossible(entity)) {
      if (entity == null) {
        throw new ElectCommonException("component.data.table.null.selected.message");
      }

      result = doIt(entity);

      if (isFireChangeEvent()) {
        fireChangeEvent();
      }
    } else {
      result = doIfActionNotPossible(entity);
    }

    return result;
  }

  /**
   * Im Normalfall ist dieses Verhalten durch den Redirect egal, wird jedoch eine Datei heruntergeladen, so gibt es keinen Redirect
   */
  protected boolean isAsyncSupported() {
    return true;
  }

  /**
   * Hat sich die Entitaet durch die Aktion geaendert, so dass das Suchergebnis neu ermittelt werden muss?
   */
  protected abstract boolean isFireChangeEvent();

  @Override
  public void executeAction(Object object) {
    try {
      E entity = (E) object;

      String result = action(entity);

      if (isNotBlank(result)) {
        refreshViewOnNonBlankResult(entity, result);
      } else {
        if (isBlockUIAnzeigen(entity)) {
          // BlockUI wird ausgeblendet, falls die Seite nicht verlassen wird.
          ajaxHelper.closeGlobalBlockUi();
        }

        if (isShowDialog()) {
          refreshViewOnNonBlankDialogName(entity, getDialogName());
        } else {
          String externalUrl = getExternalUrl(entity);

          if (externalUrl == null) {
            return;
          }

          refreshViewOnNonBlankExternalUrl(entity, externalUrl);
        }
      }
    } catch (Exception e) {
      ElectCommonExceptionHandler.handleException(e, messages);
    }
  }

  protected void refreshViewOnNonBlankResult(E entity, String result) {
    FacesContextUtil.redirectToView(result);
  }

  protected void refreshViewOnNonBlankDialogName(E entity, String dialogName) {
    openDialog(dialogName);
  }

  protected void refreshViewOnNonBlankExternalUrl(E entity, String externalUrl) {
    FacesContextUtil.redirectToExternalUrl(externalUrl);
  }

  /**
   * Aktion ausführen und ViewId für den Redirect zurückgeben oder Aktion vorbereiten und Null zurückgeben, um den mit getDialogName() angegebenen Dialog zu öffnen.
   */
  protected abstract String doIt(E entity);

  /**
   * <EntityChangeEvent> auslösen
   */
  protected abstract void fireChangeEvent();

  @Override
  public boolean isActionPossible(E entity) {
    return hasPrincipalRequiredPermission() && !isDisabled(entity);
  }

  @Override // ELECT-9273
  public boolean isActionPossibleOnRowDblselect(E entity) {
    return entity != null && isActionPossible(entity) && !isDisabledInternalOnRowDblselect(entity);
  }

  protected boolean isDisabledInternalOnRowDblselect(@Nonnull E entity) {
    return false;
  }

  @Nullable
  public String doIfActionNotPossible(E entity) {
    return null;
  }

  protected String getSimpleClassName() {
    return getSimpleClassName(getClass());
  }

  protected String getSimpleClassName(Class< ? > clazz) {
    return ClassUtil.getClassSimpleName(clazz);
  }

  protected String getMessage(String msgKey, Object... params) {
    return messages.getMessageForMessageKey(msgKey, params);
  }

  protected String getMessage(Enum< ? > enumValue) {
    return messages.lokalisiere(enumValue);
  }

  protected void openDialog() {
    openDialog(getDialogName());
  }

  protected void openDialog(String dialogName) {
    ajaxHelper.openPageDialog(dialogName);
  }

  protected void closeDialog() {
    closeDialog(getDialogName());
  }

  protected void closeDialog(String dialogName) {
    ajaxHelper.closePageDialog(dialogName);
  }

  /*
   * HACK - Start, siehe https://bugs.openjdk.java.net/browse/JDK-8071693
   *
   * NOTE dass diese Methoden auch als default-Methoden ins Interface könnten, aber da default-Properties (bspw. getDialogName()) momentan nicht per EL (also bspw. bean.dialogName) aufgerufen werden
   * können, ist hier deren Implementierung notwendig.
   *
   * Siehe auch de.ivu.elect.jee.presentation.menu.AbstractBaseMenuItemEntry!
   */

  @Override
  public boolean isShowDialog() {
    return isNotBlank(getDialogName());
  }

  @Override
  @Nullable
  public String getDialogName() {
    return null;
  }

  @Override
  public String getDialogId() {
    return DialogActionListener.createDialogId(getDialogName());
  }

  @Override
  public String getDialogFormId() {
    return DialogActionListener.createDialogFormId(getDialogName());
  }

  @Override
  public String getDialogWidgetVar() {
    return DialogActionListener.createDialogWidgetVar(getDialogName());
  }

  /*
   * HACK - ENDE
   */
}
