package de.ivu.elect.jee.performance;

import javax.annotation.Nullable;
import javax.faces.context.FacesContext;
import javax.faces.event.PhaseEvent;
import javax.faces.event.PhaseId;
import javax.faces.event.PhaseListener;

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

public class PerformancePhaseListener implements PhaseListener {

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

  private static final ThreadLocal<Long> startTimeP1 = new ThreadLocal<>();

  private static final ThreadLocal<Long> startTimeP2 = new ThreadLocal<>();

  private static final ThreadLocal<Long> startTimeP3 = new ThreadLocal<>();

  private static final ThreadLocal<Long> startTimeP4 = new ThreadLocal<>();

  private static final ThreadLocal<Long> startTimeP5 = new ThreadLocal<>();

  private static final ThreadLocal<Long> startTimeP6 = new ThreadLocal<>();

  @Override
  public void afterPhase(PhaseEvent event) {
    if (PerformanceStore.isStatistikAktiviert()) {
      try {
        PhaseId phaseId = event.getPhaseId();
        ThreadLocal<Long> source = getThreadLocal(phaseId);
        if (source != null) {
          try {
            if (event.getFacesContext() == null || event.getFacesContext().getViewRoot() == null || event.getFacesContext().getViewRoot().getViewId() == null) {
              return;
            }
            Long start = source.get();
            if (start != null) {
              long duration = System.currentTimeMillis() - start;
              ViewAccessType viewAccessType = bestimmeAccessType(event.getFacesContext());
              String viewId = event.getFacesContext().getViewRoot().getViewId();
              ViewAccessTarget viewAccessTarget = new ViewAccessTarget(ViewType.VIEW, viewAccessType,
                  PhaseIdEnum.fromPhaseId(phaseId), bestimmeSessionId(event.getFacesContext()), viewId);
              PerformanceStore.addDuration(viewAccessTarget, duration);
            }
          } finally {
            source.remove();
          }
        }
      } catch (Exception e) {
        LOG.warn("Fehler bei Performance-Analyse: " + e.getMessage());
      }
    }
  }

  private String bestimmeSessionId(FacesContext facesContext) {
    try {
      String result = facesContext.getExternalContext().getSessionId(false);
      if (StringUtils.isBlank(result)) {
        return ViewAccessTarget.SESSION_ID_OHNE;
      }
      return result;
    } catch (Exception e) {
      LOG.warn("Fehler beim Bestimmen der SessionId: " + e.getMessage());
      return ViewAccessTarget.SESSION_ID_OHNE;
    }
  }

  private ViewAccessType bestimmeAccessType(FacesContext facesContext) {
    if (facesContext.getPartialViewContext() != null && facesContext.getPartialViewContext().isAjaxRequest()) {
      return ViewAccessType.AJAX;
    }
    if (facesContext.isPostback()) {
      return ViewAccessType.POST;
    }
    return ViewAccessType.GET;
  }

  @Override
  public void beforePhase(PhaseEvent event) {
    if (PerformanceStore.isStatistikAktiviert()) {
      try {
        if (event == null || event.getFacesContext() == null || event.getFacesContext().getViewRoot() == null || event.getFacesContext().getViewRoot()
            .getViewId() == null) {
          return;
        }
        ThreadLocal<Long> tl = getThreadLocal(event.getPhaseId());
        if (tl != null) {
          tl.set(System.currentTimeMillis());
        }
      } catch (Exception e) {
        LOG.warn("Fehler bei Performance-Analyse: " + e.getMessage());
      }
    }
  }

  @Override
  public PhaseId getPhaseId() {
    return PhaseId.ANY_PHASE;
  }



  @Nullable
  private static ThreadLocal<Long> getThreadLocal(PhaseId phaseId) {
    if (phaseId == PhaseId.RESTORE_VIEW) {
      return startTimeP1;
    } else if (phaseId == PhaseId.APPLY_REQUEST_VALUES) {
      return startTimeP2;
    } else if (phaseId == PhaseId.PROCESS_VALIDATIONS) {
      return startTimeP3;
    } else if (phaseId == PhaseId.UPDATE_MODEL_VALUES) {
      return startTimeP4;
    } else if (phaseId == PhaseId.INVOKE_APPLICATION) {
      return startTimeP5;
    } else if (phaseId == PhaseId.RENDER_RESPONSE) {
      return startTimeP6;
    }
    LOG.warn("Unbekannte phaseId: " + phaseId);
    return null;
  }

}
