package com.crackwillow.struts.util.dispatch;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Enumeration;
import java.util.HashMap;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.util.MessageResources;

import com.crackwillow.log.StdOut;

public class DispatchUtil {
  protected static    Log              log      = LogFactory.getLog(DispatchUtil.class);
  protected static    MessageResources messages = MessageResources.getMessageResources ("org.apache.struts.actions.LocalStrings");
  protected           HashMap          methods  = new HashMap();
  protected           Class []         types    = { ActionMapping.class,
                                                    ActionForm.class,
                                                    HttpServletRequest.class,
                                                    HttpServletResponse.class };

  public ActionForward dispatch(Action action,
                                ActionMapping mapping,
                                ActionForm form,
                                HttpServletRequest request,
                                HttpServletResponse response)
      throws Exception {
    StdOut.log("log.button","ENTRY");
    String methodName  = getMethodName(request,mapping);
    StdOut.log("log.button","methodName = " + methodName);
    Class  clazz = action.getClass();

    if ("execute".equals(methodName) || "perform".equals(methodName)){
      // Prevent recursive calls
      String message = messages.getMessage("dispatch.recursive", mapping.getPath());
      log.error(message);
      throw new ServletException(message);
    }
    ((com.crackwillow.struts.form.LogonDispatchUtilForm)form).setMethodCalled(methodName);
    return dispatchMethod(action,clazz,mapping, form, request, response, methodName);
  }

  protected ActionForward dispatchMethod(Action action,
                                         Class clazz,
                                         ActionMapping mapping,
                                         ActionForm form,
                                         HttpServletRequest request,
                                         HttpServletResponse response,
                                         String name)
      throws Exception {
    if (name == null) {
      return this.unspecified(mapping, form, request, response);
    }

    Method method = null;

    try {
      method = getMethod(clazz,name);
    } catch(NoSuchMethodException nsme) {
      String message = messages.getMessage("dispatch.method", mapping.getPath(), name);
      log.error(message, nsme);
      throw nsme;
    }

    ActionForward forward = null;

    try {
      Object args[] = { mapping, form, request, response };
      forward = (ActionForward)method.invoke(action, args);
    } catch(ClassCastException cce) {
      String message = messages.getMessage("dispatch.return", mapping.getPath(), name);
      log.error(message, cce);
      throw cce;
    } catch(IllegalAccessException iae) {
      String message = messages.getMessage("dispatch.error", mapping.getPath(), name);
      log.error(message, iae);
      throw iae;
    } catch(InvocationTargetException ite) {
      Throwable t = ite.getTargetException();

      if (t instanceof Exception) {
        throw ((Exception) t);
      } else {
        String message = messages.getMessage("dispatch.error", mapping.getPath(), name);
        log.error(message, ite);
        throw new ServletException(t);
      }
    }
    return (forward);
  }

  protected static String getMethodName(HttpServletRequest request,  ActionMapping mapping) {
    String methodName  = null;
    String buttonValue = null;
    String paramProperty = mapping.getParameter();
    if((paramProperty != null && paramProperty.indexOf('.') != -1)) {
      methodName = paramProperty;
    } else {
      Enumeration enum = request.getParameterNames();
      while(enum.hasMoreElements()) {
        buttonValue = (String)enum.nextElement();
        if(buttonValue.indexOf(".dispatch") >= 0) {
          methodName =  buttonValue;
          break;
        }
      }
    }
    return methodName.substring(0,methodName.indexOf('.'));
  }

  protected Method getMethod(Class clazz,String name)
      throws NoSuchMethodException {
    synchronized(methods) {
      Method method = (Method) methods.get(name);

      if (method == null) {
        method = clazz.getMethod(name, types);
        methods.put(name, method);
      }

      return (method);
    }
  }

  protected ActionForward unspecified(ActionMapping mapping,
                                      ActionForm form,
                                      HttpServletRequest request,
                                      HttpServletResponse response)
      throws Exception {
    String message = messages.getMessage( "dispatch.parameter", mapping.getPath(), getMethodName(request,mapping));
    log.error(message);

    throw new ServletException(message);
  }
}