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

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

import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import javax.annotation.Nullable;
import javax.faces.event.ActionEvent;
import javax.faces.event.ActionListener;
import javax.inject.Inject;

import org.apache.commons.lang3.StringUtils;
import org.apache.deltaspike.core.spi.scope.window.WindowContext;
import org.primefaces.model.menu.MenuElement;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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.redirect.RedirectParameterAttachement;
import de.ivu.elect.jee.presentation.view.AbstractView;
import de.ivu.elect.util.ClassUtil;

public abstract class AbstractBaseMenuItemEntry implements MenuEntry, ActionListener, DialogActionListener {

  private static final Logger LOG = LoggerFactory.getLogger(AbstractBaseMenuItemEntry.class);

  private static final long serialVersionUID = 1L;

  private static final String NO_ICON = null;

  private static final String NO_TITLE = null;

  protected static final String NO_ACTION_REDIRECT = null;

  public static final String NO_ACTION_NO_REDIRECT = "NO_REDIRECT";

  public static final String WINDOW_ID = "dswid";

  public static final String TOPIC_ACTIVE_STYLE = "ui-topic-active";

  public static final String ICON_ONLY_STYLE = "ui-menuitem-link-icon-only";

  public static final String KEIN_TARGET = null;

  public static final String TARGET_EXTERN = "_blank";

  @Inject
  private transient AjaxHelper ajaxHelper;

  @Inject
  private transient Messages messages;

  @Inject
  private transient Icons icons;

  @Inject
  private transient WindowContext windowContext;

  @Inject
  private transient RedirectParameterAttachement redirectParameterAttachement;

  @Inject
  private transient JSessionMenuBuilder jSessionMenuBuilder;

  public AbstractBaseMenuItemEntry() {
    super();
  }

  @Nullable
  /**
   * Wenn Null zurückgegeben wird, wird die Action ausgeführt => getAdditionalTopicViewIds() füllen
   */
  public abstract String getTargetViewId();

  public abstract boolean isDisabled();

  public Set<String> getTopicViewIds() {
    Set<String> result = new HashSet<>(getAdditionalTopicViewIds());

    if (getTargetViewId() == null) {
      if (result.isEmpty()) {
        LOG.warn("Kein TargetViewId angegeben, und getAdditionalTopicViewIds() ist leer: " + getClass());
      }
    } else {
      result.add(getTargetViewId());
    }

    return result;
  }

  protected List<String> getAdditionalTopicViewIds() {
    return Collections.emptyList();
  }

  @Override
  public MenuElement buildComponent() {
    IVUDefaultMenuItem defaultMenuItem = new IVUDefaultMenuItem();

    defaultMenuItem.setRendered(isRendered());

    if (!isRendered()) {
      return defaultMenuItem;
    }

    // NOTE, dass dieser Pseudo-Style erwartet wird, um einen Menüeintrag typsicher per CSS-Rule oder XPath finden zu können!
    String pseudoStyleClass = ClassUtil.getClassSimpleName(getClass());

    defaultMenuItem.setStyleClass(pseudoStyleClass);
    defaultMenuItem.setDisabled(isDisabled());

    if (isNotBlank(getStyleClass())) {
      defaultMenuItem.setStyleClass(trimToEmpty(defaultMenuItem.getStyleClass()) + " " + getStyleClass());
    }

    if (isNotBlank(getContainerStyleClass())) {
      defaultMenuItem.setContainerStyleClass(getContainerStyleClass());
    }

    if (isNotBlank(getStyle())) {
      defaultMenuItem.setStyle(trimToEmpty(defaultMenuItem.getStyle()) + " " + getStyle());
    }

    if (isTopicActive()) {
      defaultMenuItem.setStyleClass(trimToEmpty(defaultMenuItem.getStyleClass()) + " " + TOPIC_ACTIVE_STYLE);
    }

    if (isShowBlockUi()) {
      // ELECT-1964 verhindern, dass BlockUI beim öffnen in neuem Tab aufgerufen wird, cf. elect-base-war/src/main/webapp/resources/js/common/PrimeFacesExt/widget/BlockUI.js!
      defaultMenuItem.setOnclick(getGlobalBlockUiStatement());
    }

    if (StringUtils.isNotBlank(getOnClick())) {
      defaultMenuItem.setOnclick(getOnClick());
    }

    if (defaultMenuItem.isDisabled()) {
      defaultMenuItem.setUrl("#");
    } else {
      defaultMenuItem.setImmediate(true);

      if (getTargetViewId() == null) {
        defaultMenuItem.addActionListener(this);
        // Wieso weshalb warum, siehe: MenuItemActionListenerController
        defaultMenuItem.setCommand("#{" + MenuItemActionListenerController.COMPONENT_NAME + ".event}");

        if (isAjax()) {
          defaultMenuItem.setAjax(true);
        }
      } else {
        defaultMenuItem.setUrl(buildUrlForTargetViewId(getTargetViewId()));

        if (isNotBlank(getTarget())) {
          defaultMenuItem.setTarget(getTarget());
        }
      }
    }

    defaultMenuItem.setValue(getLabel());

    if (isNotBlank(getTitle())) {
      defaultMenuItem.setTitle(getTitle());
    }

    if (isNotBlank(getIcon())) {
      defaultMenuItem.setIcon(getIcon());
    }

    if (isShowDialog()) {
      // NOTE ELECT-1958, falls ein (modaler) Dialog geöffnet wird, zunächst globale BlockUI ausblenden, da diese
      // ansonsten, bspw. nach Auswahl von 'Abbrechen', angezeigt bleibt und NICHT SCHLIESSBAR ist.
      defaultMenuItem.setOncomplete(ajaxHelper.createCloseGlobalBlockUiStatement() + " " + ajaxHelper.createOpenPageDialogStatement(getDialogName()));
      defaultMenuItem.setUpdate(":" + getDialogFormId());
    } else {
      // NOTE, dass beim Navigieren per Menü kein AJAX-Request gefeuert werden soll, wenn nicht explizit gewünscht
      if (!isAjax()) {
        defaultMenuItem.setAjax(false);
      }
    }

    return defaultMenuItem;
  }

  protected String getGlobalBlockUiStatement() {
    return ajaxHelper.createGlobalBlockUiStatement("blockLink", "event");
  }

  protected String getOnClick() {
    return "";
  }

  @Override
  public boolean isTopicActive() {
    String currentViewId = FacesContextUtil.getCurrentViewId();
    return StringUtils.isNotBlank(currentViewId) && getTopicViewIds().contains(currentViewId);
  }

  protected boolean isAjax() {
    return false;
  }

  protected boolean isShowBlockUi() {
    return false;
  }

  @Nullable
  public String getTarget() {
    return KEIN_TARGET;
  }

  protected String buildRedirectForTargetViewId(String viewID) {
    if (viewID == null) {
      LOG.warn("buildRedirectForTargetViewId, viewID ist null: " + getClass());

      return null;
    }

    StringBuilder sb = new StringBuilder(viewID);

    sb.append(jSessionMenuBuilder.getJsessionId());

    sb.append("?" + WINDOW_ID + "=").append(windowContext.getCurrentWindowId());

    if (isResetViewStackAllowed()) {
      sb.append("&").append(AbstractView.RESET_VIEW_STACK_REQUEST_PARAMETER_NAME).append("=").append(AbstractView.RESET_VIEW_STACK_REQUEST_VALUE);
    }

    if (isIncludePageParameter()) {
      attachParameterMap(sb);
    }

    return sb.toString();
  }

  @Nullable
  protected String buildUrlForTargetViewId(String viewID) {
    if (viewID == null) {
      LOG.warn("buildUrlForTargetViewId, viewID ist null: " + getClass());

      return null;

    }
    return FacesContextUtil.getContextPath() /* ELECT-7825 */ + buildRedirectForTargetViewId(viewID);
  }

  protected void attachParameterMap(StringBuilder sb) {
    sb.append(redirectParameterAttachement.getParameterMap().entrySet().stream().map(entry -> "&" + entry.getKey() + "=" + entry.getValue()).collect(Collectors.joining()));
  }

  /**
   * Soll bei einer Weiterleitung auf einen anderen View der ViewStack zurückgesetzt werden? Dies verhindert, dass alte, nicht mehr benötigte Views im Speicher verbleiben!
   */
  protected boolean isResetViewStackAllowed() {
    return true;
  }

  protected boolean isIconOnly() {
    return false;
  }

  public String getLabelFinal() {
    return getLabel();
  }

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

  public String getTitle() {
    return NO_TITLE;
  }

  public String getIcon() {
    return NO_ICON;
  }

  public String getStyleClass() {
    if (isIconOnly()) {
      return ICON_ONLY_STYLE;
    }
    return "";
  }

  public String getContainerStyleClass() {
    return "";
  }

  public String getStyle() {
    return "";
  }

  @Override
  public void processAction(ActionEvent event) {
    String viewId = doAction();

    if (isAjax()) {
      return; // nichts zu tun...
    }

    if (NO_ACTION_NO_REDIRECT.equals(viewId)) {
      return;
    }

    if (viewId == null) {
      viewId = FacesContextUtil.getCurrentViewId();
    }

    if (isIncludePageParameter()) {
      FacesContextUtil.redirectToView(buildRedirectForTargetViewId(viewId));
    } else {
      FacesContextUtil.redirectToViewNoRedirectParameter(buildRedirectForTargetViewId(viewId));
    }
  }

  public String doAction() {
    return NO_ACTION_REDIRECT;
  }

  protected String getMessage(String key, Object... params) {
    return getMessages().getMessageForMessageKey(key, params);
  }

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

  protected void addInfoMessage(String msgKey, Object... params) {
    getMessages().addInfoMessage(msgKey, params);
  }

  protected void addWarnMessage(String msgKey, Object... params) {
    getMessages().addWarnMessage(msgKey, params);
  }

  protected void addErrorMessage(String msgKey, Object... params) {
    getMessages().addErrorMessage(msgKey, params);
  }

  protected Messages getMessages() {
    return messages;
  }

  protected Icons getIcons() {
    return icons;
  }

  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.action.AbstractBaseActionEntry!
   */

  @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
   */

  protected boolean isIncludePageParameter() {
    return true;
  }
}
