
package de.ivu.elect.jee.faces.security;

import java.util.ArrayList;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import javax.inject.Inject;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;

import de.ivu.elect.jee.exception.ElectCommonException;
import de.ivu.elect.jee.faces.Escape;
import de.ivu.elect.jee.faces.Messages;
import de.ivu.elect.jee.log.ElectMDC;
import de.ivu.elect.jee.security.AbstractPrincipal;
import de.ivu.elect.jee.security.Permission;
import de.ivu.elect.jee.security.SecurityException;

public abstract class AbstractSecurityViewValidator<UseCasePermission extends Permission, View> implements SecurityViewValidatorMarker {

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

  public static final String SECURITY_VIEW_VALIDATOR_NAME = "securityViewValidator";

  private static final Map<String, Object> VIEW_ANNOTATION_FOR_ID_MAP = new ConcurrentHashMap<>();

  @Inject
  protected transient AbstractPrincipal<UseCasePermission, ?, ?, ?> principal;

  @Inject
  private transient Messages messages;

  @Inject
  protected transient Escape escape;

  protected abstract Iterable<Object> getAllViews();

  @Override
  public void checkView(String viewId) {
    setLoggingParameter();
    if (StringUtils.isNotBlank(viewId)) {
      Object cached = VIEW_ANNOTATION_FOR_ID_MAP.get(viewId);
      if (cached != null) {
        checkViewSecureAnnotation((View) cached, viewId);
        return;
      }
    }

    for (Object view : getAllViews()) {
      if (checkView(view, viewId)) {
        return;
      }
    }
    LOG.warn("View-Annotation nicht gefunden: " + escape.encodeUrl(viewId));
    throw new ElectCommonException(ElectCommonException.RedirectDestination.START_VIEW, "no.view.for.view.id.found", escape.encodeUrl(viewId));
  }

  protected void setLoggingParameter() {
    try {
      if (principal.isLoggedIn()) {
        MDC.put(ElectMDC.USER_PARAMETER, getLoginUserId());
        MDC.put(ElectMDC.USER_NAME_PARAMETER, getLoginUsername());
      } else {
        MDC.put(ElectMDC.USER_PARAMETER, ElectMDC.USER_NO_VALUE);
        MDC.put(ElectMDC.USER_NAME_PARAMETER, ElectMDC.USER_NAME_NO_VALUE);
      }
    } catch (Exception e){
      LOG.warn("Fehler beim Setzen des Logging-Parameters: " + e.getMessage());
    }
  }

  protected String getLoginUsername() {
    return principal.getName();
  }

  protected String getLoginUserId() {
    return principal.getUuid();
  }

  @SuppressWarnings("unchecked")
  private boolean checkView(Object view, String viewId) {
    View viewAnnotation = (View) view.getClass().getAnnotation(getViewAnnotationClass());

    if (getViewId(viewAnnotation).equals(viewId)) {
      VIEW_ANNOTATION_FOR_ID_MAP.put(viewId, viewAnnotation);
      checkViewSecureAnnotation(viewAnnotation, viewId);
      return true;
    }
    return false;
  }

  protected abstract String getViewId(View viewAnnotation);

  @SuppressWarnings("rawtypes")
  protected abstract Class getViewAnnotationClass();

  private void checkViewSecureAnnotation(View view, String viewId) {

    if (isKeinUseCaseAngegben(view)) {
      // wenn keine UseeCasePermission bei der Definition des Views angegeben wurde
      LOG.error("Keine UseCasePermission an der @View Annotation für den '" + escape.encodeUrl(viewId) + "' angegeben!");
      throw new IllegalStateException("UseCasePermission fehlt '" + escape.encodeUrl(viewId) + "'!");
    }
    if (!isPermissionErfuellt(view)) {
      throw new SecurityException(messages, escape.encodeUrl(viewId), getRequiredPermission(view), new ArrayList<>(principal.getUseCasePermissions()));
    }
  }

  protected abstract UseCasePermission getRequiredPermission(View view);

  protected abstract boolean isPermissionErfuellt(View view);

  protected abstract boolean isKeinUseCaseAngegben(View view);

  public static void cache(String viewId, Object annotation) {
    if (viewId != null && annotation != null) {
      VIEW_ANNOTATION_FOR_ID_MAP.put(viewId, annotation);
    }
  }
}
