/*
  Copyright (C) 2006 Helge Hess

  This file is part of JOPE.

  JOPE is free software; you can redistribute it and/or modify it under
  the terms of the GNU Lesser General Public License as published by the
  Free Software Foundation; either version 2, or (at your option) any
  later version.

  JOPE is distributed in the hope that it will be useful, but WITHOUT ANY
  WARRANTY; without even the implied warranty of MERCHANTABILITY or
  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
  License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with JOPE; see the file COPYING.  If not, write to the
  Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
  02111-1307, USA.
*/

package org.opengroupware.jope.appserver.core;

import static org.opengroupware.jope.foundation.NSJavaRuntime.NSAllocateObject;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.opengroupware.jope.appserver.publisher.JoContext;

public class WODirectActionRequestHandler extends WORequestHandler {

  protected final Log daLog = LogFactory.getLog("WODirectActions");
  
  public WODirectActionRequestHandler(WOApplication _app) {
    super(_app);
  }
  
  /* Zope style :action form values */
  
  public String formActionFromRequest(WORequest _rq) {
    // TODO: Zope can also do other form value conversions, maybe we want to
    //       support them as well. (see Zope DevGuide, Object Publishing)
    if (_rq == null) return null;
    
    String[] formKeys = _rq.formValueKeys();
    if (formKeys == null)
      return null;
    
    for (String formValueKey: formKeys) {
      int l = formValueKey.length();
      if (l < 7) continue;
      
      if (l >= 7 && formValueKey.endsWith(":action")) {
        return l == 7
          ? _rq.stringFormValueForKey(formValueKey)
          : formValueKey.substring(0, l - 7);
      }
      
      /* Note: image submits have no values (only coordinates) */
      if (l > 9 && formValueKey.endsWith(":action.x"))
        return formValueKey.substring(0, l - 9);
    }
    return null;
  }
  
  /* request handling */

  public WOResponse handleRequest(WORequest _rq, WOContext _ctx, WOSession _s) {
    // DEPRECATED, this object also acts as a JoMethod
    boolean isDebugOn = this.daLog.isDebugEnabled();
    WOResponse response      = null;
    String   actionClassName = null;
    String   actionName;
    String[] handlerPath;
    
    /* first we scan form parameters for a ":action" form value */
    
    if ((actionName = this.formActionFromRequest(_rq)) != null) {
      if (isDebugOn)
        this.daLog.debug("located action in form values: " + actionName);
      
      int idx = actionName.indexOf('/');
      if (idx != -1) {
        actionClassName = actionName.substring(0, idx);
        actionName      = actionName.substring(idx + 1);
      }
    }
    
    /* decode URL */
    
    handlerPath = _rq.requestHandlerPathArray();
    switch (handlerPath.length) {
      case 0: {
        if (actionClassName == null) actionClassName = "DirectAction";
        if (actionName      == null) actionName      = "default";
        if (isDebugOn)
          this.daLog.debug("no DA path, using DirectAction/default");
        break;
      }
      case 1: {
        if (actionName != null) {
          /* form name overrides path values */
          if (actionClassName == null)
            actionClassName = handlerPath[0];
          if (isDebugOn)
            this.daLog.debug("single form path, action:" + actionName);
        }
        else {
          actionClassName = "DirectAction";
          actionName      = handlerPath[0];
          if (isDebugOn)
            this.daLog.debug("simple path, using DirectAction/" + actionName);
        }
        break;
      }
      default: {
        if (actionClassName == null) actionClassName = handlerPath[0];
        if (actionName      == null) actionName      = handlerPath[1];
        if (isDebugOn)
          this.daLog.debug("full path: " + actionClassName + "/" + actionName);
        break;
      }
    }
    
    if (actionClassName.length() == 0) actionClassName = "DirectAction";
    if (actionName.length()      == 0) actionName      = "default";
    
    int idx;
    
    /* discard everything after a point, to allow for better download URLs */
    if ((idx = actionName.indexOf('.')) != -1)
      actionName = actionName.substring(0, idx);
 
    /* find direct action class */
    
    WOResourceManager rm = _ctx.application().resourceManager();
    Class daClass = rm.lookupClass(actionClassName);
    
    if (daClass == null) {
      this.daLog.error("did not find action class: "+ actionClassName);
      return null;
    }
    else if (isDebugOn)
      this.daLog.debug("using DA class: " + daClass);
    
    /* setup fragment rendering mode */
    
    if (_rq.isFragmentIDInRequest()) {
      /* for fragments, we initially disable rendering of elements */
      _ctx.disableRendering();
    }
    
    /* instantiate object and call it */
    
    Object results;
    if (WOComponent.class.isAssignableFrom(daClass)) {
      WOComponent page;
      
      page = rm.pageWithName(actionClassName, _ctx);
      if (page == null) {
        this.daLog.error("could not instantiate page: "+ actionClassName);
        return null;
      }
      
      _ctx.setPage(page);
      page.ensureAwakeInContext(_ctx);
      _ctx.enterComponent(page, null);
      {
        if (page.shouldTakeValuesFromRequest(_rq, _ctx))
          page.takeValuesFromRequest(_rq, _ctx);
        
        // TODO: can we move this to invokeAction() somehow?
        results = page.performActionNamed(actionName);
      }
      _ctx.leaveComponent(page);
      _ctx.setPage(null);
      
      if (results == null)
        results = page;
    }
    else {
      WOAction da = (WOAction)NSAllocateObject(daClass, WOContext.class, _ctx);
      if (da == null) {
        this.daLog.error("could not instantiate action: "+ actionClassName);
        return null;
      }

      results = da.performActionNamed(actionName);
    }
    
    /* process results */
    // TODO: this should be done in a renderer
    
    if (results instanceof WOComponent) {
      /* reuse context response for WOComponent */
      WOComponent page = (WOComponent)results;
      
      if (isDebugOn) this.daLog.debug("delivering page: " + page);
      
      _ctx.setPage(page);
      page.ensureAwakeInContext(_ctx);
      _ctx.enterComponent(page, null);
      {
        response = _ctx.response();
        page.appendToResponse(response, _ctx);
      }
      _ctx.leaveComponent(page);
    }
    else if (results instanceof WOActionResults) {
      if (isDebugOn) this.daLog.debug("delivering results: " + results);
      response = ((WOActionResults)results).generateResponse();
    }
    else if (results instanceof String) {
      if (isDebugOn) this.daLog.debug("delivering string: " + results);
      response = _ctx.response();
      response.appendContentHTMLString((String)results);
    }
    // TODO: add support for HTTP status exceptions
    else if (results != null) {
      this.daLog.error("unexpected result: " + results);
      response = _ctx.response();
      response.setStatus(WOMessage.HTTP_STATUS_INTERNAL_ERROR);
    }
    
    /* tear down */
    // TODO: tear down, sleep components etc
    
    return response;
  }
  
  public Object callInContext(Object _object, JoContext _ctx) {
    boolean isDebugOn = this.daLog.isDebugEnabled();
    
    if (_ctx == null) {
      if (isDebugOn) log.debug("got no context");
      return null;
    }
    
    // TODO: would be better if this would somehow patch the path in the context
    //       and then setup/call a JoActionInvocation which is the same thing
    //       used to bind actions to arbitary JoObjects.

    WOContext wctx = (WOContext)_ctx;
    String    actionClassName = null;
    String    actionName;
    
    /* first we scan form parameters for a ":action" form value */
    // TODO: this should be moved to WOApp traversalPathForRequest
    
    if ((actionName = this.formActionFromRequest(wctx.request())) != null) {
      if (isDebugOn)
        this.daLog.debug("located action in form values: " + actionName);
      
      int idx = actionName.indexOf('/');
      if (idx != -1) {
        actionClassName = actionName.substring(0, idx);
        actionName      = actionName.substring(idx + 1);
      }
    }
    
    /* decode path */
    
    String[] handlerPath = _ctx.joTraversalPath().pathInfo();
    if (handlerPath == null) handlerPath = new String[0];
    switch (handlerPath.length) {
      case 0: {
        if (actionClassName == null) actionClassName = "DirectAction";
        if (actionName      == null) actionName      = "default";
        if (isDebugOn)
          this.daLog.debug("no DA path, using DirectAction/default");
        break;
      }
      case 1: {
        if (actionName != null) {
          /* form name overrides path values */
          if (actionClassName == null)
            actionClassName = handlerPath[0];
          if (isDebugOn)
            this.daLog.debug("single form path, action:" + actionName);
        }
        else {
          actionClassName = "DirectAction";
          actionName      = handlerPath[0];
          if (isDebugOn)
            this.daLog.debug("simple path, using DirectAction/" + actionName);
        }
        break;
      }
      default: {
        if (actionClassName == null) actionClassName = handlerPath[0];
        if (actionName      == null) actionName      = handlerPath[1];
        if (isDebugOn)
          this.daLog.debug("full path: " + actionClassName + "/" + actionName);
        break;
      }
    }
    
    if (actionClassName.length() == 0) actionClassName = "DirectAction";
    if (actionName.length()      == 0) actionName      = "default";
    
    int idx;
    
    /* discard everything after a point, to allow for better download URLs */
    if ((idx = actionName.indexOf('.')) != -1)
      actionName = actionName.substring(0, idx);
 
    /* find direct action class */
    
    WOResourceManager rm = wctx.application().resourceManager();
    Class daClass = rm.lookupClass(actionClassName);
    
    if (daClass == null) {
      this.daLog.error("did not find action class: "+ actionClassName);
      return null;
    }
    else if (isDebugOn)
      this.daLog.debug("using DA class: " + daClass);
    
    /* instantiate object and call it */
    
    Object results;
    if (WOComponent.class.isAssignableFrom(daClass)) {
      WOComponent page;
      
      page = rm.pageWithName(actionClassName, wctx);
      if (page == null) {
        this.daLog.error("could not instantiate page: "+ actionClassName);
        return null;
      }
      
      wctx.setPage(page);
      page.ensureAwakeInContext(wctx);
      wctx.enterComponent(page, null);
      {
        if (page.shouldTakeValuesFromRequest(wctx.request(), wctx))
          page.takeValuesFromRequest(wctx.request(), wctx);
        
        // TODO: can we move this to invokeAction() somehow?
        results = page.performActionNamed(actionName);
      }
      wctx.leaveComponent(page);
      wctx.setPage(null);
      
      if (results == null)
        results = page;
    }
    else {
      WOAction da = (WOAction)NSAllocateObject(daClass, WOContext.class, _ctx);
      if (da == null) {
        this.daLog.error("could not instantiate action: "+ actionClassName);
        return null;
      }

      results = da.performActionNamed(actionName);
    }
    
    return results;
  }

}
