/*
  Copyright (C) 2002-2004 SKYRIX Software AG

  This file is part of OpenGroupware.org.

  OGo 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.

  OGo 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 OGo; see the file COPYING.  If not, write to the
  Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
  02111-1307, USA.
*/
// $Id$

#include "SoObjectXmlRpcDispatcher.h"
#include "SoObject.h"
#include "NSException+HTTP.h"
#include <NGXmlRpc/XmlRpcMethodCall+WO.h>
#include <NGXmlRpc/XmlRpcMethodResponse+WO.h>
#include <NGObjWeb/WOActionResults.h>
#include <NGObjWeb/WOContext.h>
#include <NGObjWeb/WOResponse.h>
#include "common.h"

@interface NSObject(XmlRpcCall)

- (id)callOnObject:(id)_client 
  withPositionalParameters:(NSArray *)_args
  inContext:(id)_ctx;

@end

@implementation SoObjectXmlRpcDispatcher

static BOOL debugOn = NO;

+ (void)initialize {
  NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
  
  debugOn = [ud boolForKey:@"SoObjectXmlRpcDispatcherDebugEnabled"];
  if (debugOn) NSLog(@"Note: SOPE XML-RPC Dispatcher Debugging turned on.");
}

/* error handling */

- (NSException *)missingMethodFault:(NSString *)_method 
  inContext:(WOContext *)_ctx
{
  NSString *r;
  
  r = [@"Could not locate requested XML-RPC method: " 
	stringByAppendingString:_method];
  return [NSException exceptionWithHTTPStatus:404 /* not found */
		      reason:r];
}

/* perform call on object */

- (id)performActionNamed:(NSString *)_name parameters:(NSArray *)_params 
  inContext:(WOContext *)_ctx
{
  NSString *methodName;
  NSRange  r;
  id       clientObject;
  id       method;

  // TODO: check whether _name is set
  
  [self debugWithFormat:@"should perform: %@", _name];
  methodName = nil;
  
  r = [_name rangeOfString:@"." options:(NSLiteralSearch|NSBackwardsSearch)];
  if (r.length > 0) {
    /* has a prefix, eg "folder.folder.a()" => traverse */
    NSArray     *nsParts;
    NSException *error = nil;
    unsigned    count;
    
    nsParts    = [_name componentsSeparatedByString:@"."];
    count      = [nsParts count];
    methodName = [[[nsParts objectAtIndex:(count - 1)] copy] autorelease];
    nsParts    = [nsParts subarrayWithRange:NSMakeRange(0, count - 1)];
    
    [self debugWithFormat:@"XML-RPC traversal: %@",
	    [nsParts componentsJoinedByString:@" => "]];
    
    /*
       TODO: we might not want to use -traverse.. so that the clientObject
             stays the same (the one bound to the URL)?
    */
    clientObject = [self->object 
			traversePathArray:nsParts inContext:_ctx error:&error
			acquire:YES];
    if (error) {
      [self debugWithFormat:@"  XML-RPC traversal error: %@", error];
      return error;
    }
  }
  else {
    clientObject = self->object;
    methodName   = _name;
  }
  
  method = [clientObject lookupName:methodName inContext:_ctx acquire:YES];
  if (method == nil) {
    // TODO: return proper fault!
    [self logWithFormat:@"did not find requested XML-RPC method: '%@'",
	    methodName];
    return [self missingMethodFault:methodName inContext:_ctx];
  }
  if (![method isCallable]) {
    // TODO: return proper fault!
    [self logWithFormat:
	    @"located object (%@) is not callable (class=%@):\n  %@", 
	    methodName, NSStringFromClass([method class]), method];
    return nil;
  }
  
  /* TODO: do we need to bind or is this automatic? */
  
  if ([method respondsToSelector:
		@selector(callOnObject:withPositionalParameters:inContext:)]) {
    [self debugWithFormat:
	    @"calling XML-RPC method with %i positional parameters.",
	    [_params count]];
    return [method callOnObject:clientObject 
		   withPositionalParameters:_params 
		   inContext:_ctx];
  }
  
  if ([_params count] > 0) {
    [self logWithFormat:
	    @"WARNING: invoking SOPE method via XML-RPC without "
	    @"positional paramters (%i parameters defined)",
	    [_params count]];
  }
  return [method callOnObject:clientObject inContext:_ctx];
}

- (id)faultFromException:(NSException *)_exception
  methodCall:(XmlRpcMethodCall *)_call
{
  /* add some more information to generic exceptions ... */
  if (_call) {
    NSMutableDictionary *ui;
    
    ui = [[_exception userInfo] mutableCopy];
    if (ui == nil) ui = [[NSMutableDictionary alloc] init];
    
    [ui setObject:[_call methodName] forKey:@"methodName"];
    [ui setObject:[_call parameters] forKey:@"methodParameters"];
    
    [_exception setUserInfo:ui];
    [ui release];
  }
  
  [self logWithFormat:@"%s: turning exception into fault %@\n",
          __PRETTY_FUNCTION__,
          [_exception description]];
  
  return _exception;
}

- (id<WOActionResults>)actionResponseForResult:(id)resValue {
  if ([resValue conformsToProtocol:@protocol(WOActionResults)]) {
    /* a "HTTP" result ... */
    return resValue;
  }
  else {
    /* an XML-RPC result ... */
    XmlRpcMethodResponse *mResponse;
    
    mResponse = [[XmlRpcMethodResponse alloc] initWithResult:resValue];
    return [mResponse autorelease];
  }
}

- (id)performMethodCall:(XmlRpcMethodCall *)_call inContext:(WOContext *)_ctx{
  id resValue;
  
  NS_DURING {
    resValue = [self performActionNamed:[_call methodName]
                     parameters:[_call parameters]
		     inContext:_ctx];
    resValue = [resValue retain];
  }
  NS_HANDLER {
    resValue = [self faultFromException:localException
                     methodCall:_call];
    resValue = [resValue retain];
  }
  NS_ENDHANDLER;
  
  resValue = [resValue autorelease];
  
  return [self actionResponseForResult:resValue];
}

- (id)couldNotDecodeXmlRpcRequestInContext:(WOContext *)_ctx {
  WOResponse *r = [_ctx response];
  
  [r setStatus:400 /* bad request */];
  [r appendContentString:@"malformed XML-RPC request !"];
  return r;
}

- (id)handleXmlRpcEncodingException:(NSException *)_exception object:(id)_obj {
  [self logWithFormat:@"could not encode object: %@ (%@): %@",
	  _obj, NSStringFromClass([_obj class]), _exception];
  return nil;
}

- (id)dispatchInContext:(WOContext *)_ctx {
  XmlRpcMethodResponse *r;
  XmlRpcMethodCall *call;
  id result;
  
  call = [XmlRpcMethodCall alloc];
  call = [call initWithRequest:[_ctx request]];
  call = [call autorelease];
  
  if (call == nil)
    return [self couldNotDecodeXmlRpcRequestInContext:_ctx];
  
  if ((result = [self performMethodCall:call inContext:_ctx]) == nil)
    /* TODO: should we return a fault instead? */
    return nil;
  
  if ([result isKindOfClass:[WOResponse class]])
    /* pass WOResponse objects through ... */
    return result;
  
  NS_DURING {
    if ([result isKindOfClass:[XmlRpcMethodResponse class]])
      r = result;
    else
      r = [[[XmlRpcMethodResponse alloc] initWithResult:result] autorelease];
    
    result = [[[r generateResponse] retain] autorelease];
  }
  NS_HANDLER {
    result = [self handleXmlRpcEncodingException:localException
		   object:result];
  }
  NS_ENDHANDLER;
  
  return result;
}

/* debugging */

- (NSString *)loggingPrefix {
  return @"[obj-xmlrpc-dispatch]";
}
- (BOOL)isDebuggingEnabled {
  return debugOn;
}

@end /* SoObjectXmlRpcDispatcher */
