/*
  Copyright (C) 2000-2005 SKYRIX Software AG

  This file is part of SOPE.

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

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

/*
  xmlrpc_call
  
  A neat tool to call XML-RPC servers from the shell.

  Defaults:
    login     - string
    password  - string
    forceauth - bool   - send the credentials in the first request!
*/

#include <NGXmlRpc/NGXmlRpcClient.h>

#include <NGExtensions/NSString+Ext.h>
#include <NGStreams/NGStreams.h>
#include <NGStreams/NGNet.h>

#if !LIB_FOUNDATION_LIBRARY
#  include <NGObjWeb/UnixSignalHandler.h>
#endif

#include "common.h"

@class WOResponse;

@interface NGXmlRpcClient(CallFailed)
- (id)callFailed:(WOResponse *)_response;
@end /* NGXmlRpcClient */

@class HandleCredentialsClient;

@interface XmlRpcClient : NSObject
{
  HandleCredentialsClient  *client;
  NSString                 *methodName;
  NSArray                  *parameters;
}

/* initialization */

- (id)initWithArguments:(NSArray *)_arguments;
- (void)initMethodCall:(NSArray *)_arguments;
- (BOOL)initXmlRpcClientWithStringURL:(NSString *)_url;

- (int)run;
- (void)help:(NSString *)pn;

- (void)printElement:(id)_element;
- (void)printDictionary:(NSDictionary *)_dict;
- (void)printArray:(NSArray *)_array;

@end /* XmlRpcClient */

@implementation NSObject(Printing)

- (void)printWithTool:(XmlRpcClient *)_tool {
  printf("%s\n", [[self description] cString]);
}

@end /* NSObject(Printing) */

@implementation NSData(Printing)

- (void)printWithTool:(XmlRpcClient *)_tool {
  fwrite([self bytes], [self length], 1, stdout);
}

@end /* NSData(Printing) */

@implementation NSDictionary(Printing)

- (void)printWithTool:(XmlRpcClient *)_tool {
  [_tool printDictionary:self];
}

@end /* NSDictionary(Printing) */

@implementation NSArray(Printing)

- (void)printWithTool:(XmlRpcClient *)_tool {
  [_tool printArray:self];
}

@end /* NSArray(Printing) */

@implementation NSException(Printing)

- (void)printWithTool:(XmlRpcClient *)_tool {
  printf("Exception caught\nName  : %s\nReason: %s\n",
         [[self name] cString], [[self reason] cString]);
}

@end /* NSException(Printing) */

@interface HandleCredentialsClient : NGXmlRpcClient
{
  NSString *defLogin;
  NSString *defPassword;
}

/* accessors */

- (void)setDefLogin:(NSString *)_login;
- (void)setDefPassword:(NSString *)_pwd;

@end /* HandleCredentialsClient */

#include <unistd.h>
#include <NGObjWeb/WOResponse.h>

@implementation HandleCredentialsClient

- (void)dealloc {
  [self->defLogin    release];
  [self->defPassword release];
  [super dealloc];
}

/* accessors */

- (void)setDefLogin:(NSString *)_login {
  ASSIGNCOPY(self->defLogin, _login);
}
- (void)setDefPassword:(NSString *)_pwd {
  ASSIGNCOPY(self->defPassword, _pwd);
}

/* prompting */

- (NSString *)prompt:(NSString *)_prompt {
  NSString *login;
  char clogin[256];
  
  fprintf(stderr, "%s", [_prompt cString]);
  fflush(stderr);
  fgets(clogin, 200, stdin);
  clogin[strlen(clogin) - 1] = '\0';
  login = [NSString stringWithCString:clogin];
  return login;
}

- (NSString *)promptPassword:(NSString *)_prompt {
  NSString *pwd;
  char     *cpwd;

  cpwd = getpass("password: ");
  pwd = [NSString stringWithCString:cpwd];
  return pwd;
}

- (id)callFailed:(WOResponse *)_response {
  if ([_response status] == 401) {
    NSString *wwwauth;
    NSString *user;
    NSString *pass;
    
    wwwauth = [_response headerForKey:@"www-authenticate"];
    if ([[wwwauth lowercaseString] hasPrefix:@"digest"])
      [self logWithFormat:@"Digest authentication:\n'%@'", wwwauth];

    // TODO: test credentials of URL
    
    if (self->defLogin) {
      user = [self->defLogin autorelease];
      self->defLogin = nil;
    }
    else
      user = [self prompt:@"login:    "];
    
    if (self->defPassword) {
      pass = [self->defPassword autorelease];
      self->defPassword = nil;
    }
    else
      pass = [self promptPassword:@"password: "];
    
    [self setUserName:user];
    [self setPassword:pass];
    
    /* this "should" return some kind of "need-pwd" object ... */
    return nil;
  }
  else {
    return [super callFailed:_response];
  }
}

@end /* HandleCredentialsClient */

#define EXIT_FAIL -1

@implementation XmlRpcClient

/* initialization */

- (id)init {
  return [self initWithArguments:nil];
}

- (id)initWithArguments:(NSArray *)_arguments {
  if ((self = [super init])) {
    NSUserDefaults *ud;
    NSString *s;
    int argc;

    argc = [_arguments count];
    if(argc == 1) {
      [self help:[_arguments objectAtIndex:0]];
      return nil;
    }
    
    s = [_arguments objectAtIndex:1];
    if (![self initXmlRpcClientWithStringURL:s]) {
      printf("Error initializing the XML-RPC client\n");
      [self release];
      return nil;
    }
    
    if (argc > 2) {
      [self initMethodCall:_arguments];
    }
    else {
      self->methodName = @"system.listMethods";
      self->parameters = nil;
    }
    
    ud = [NSUserDefaults standardUserDefaults];
    if ([ud boolForKey:@"forceauth"]) {
      [self->client setUserName:[ud stringForKey:@"login"]];
      [self->client setPassword:[ud stringForKey:@"password"]];
    }
    else {
      [self->client setDefLogin:[ud stringForKey:@"login"]];
      [self->client setDefPassword:[ud stringForKey:@"password"]];
    }
  }
  return self;
}

- (BOOL)initXmlRpcClientWithStringURL:(NSString *)_url {
  NSURL *url = nil;
  
  if (![_url isAbsoluteURL]) {
    /* make a raw, Unix domain socket connection */
    NGLocalSocketAddress *addr;
    
    addr = [NGLocalSocketAddress addressWithPath:_url];
    self->client = [[HandleCredentialsClient alloc] initWithRawAddress:addr];
    return YES;
  }
  
  if ((url = [NSURL URLWithString:_url]) != nil) {
#if 0
    if ((uri = [url path]) == nil)
      uri = @"/RPC2";
#endif
    
    self->client = [[HandleCredentialsClient alloc] initWithURL:url];
    return YES;

  }
  else {
    printf("Invalid URL\n");
    return NO;
  }
}

- (void)initMethodCall:(NSArray *)_arguments {
  self->methodName = [_arguments objectAtIndex:2];

  if ([_arguments count] > 2) {
    NSRange range = NSMakeRange(3, [_arguments count] - 3);
    self->parameters = [[_arguments subarrayWithRange:range] retain];
  }
}

- (void)dealloc {
  [self->client      release];
  [self->methodName  release];
  [self->parameters  release];
  [super dealloc];
}

/* printing */

- (void)printElement:(id)element {
  [element printWithTool:self];
}

/* printing objects */

- (void)printDictionary:(NSDictionary *)_dict {
  NSEnumerator *dictEnum;
  id dictKey;

  dictEnum = [_dict keyEnumerator];

  while((dictKey = [dictEnum nextObject])) {
    printf("%s=", [dictKey cString]);    
    [self printElement:[_dict objectForKey:dictKey]];
  }
}

- (void)printArray:(NSArray *)_array {
  NSEnumerator *arrayEnum;
  id arrayElem;
  
  arrayEnum = [_array objectEnumerator];
  while((arrayElem = [arrayEnum nextObject])) {
    [self printElement:arrayElem];
  }
}

- (void)help:(NSString *)pn {
  fprintf(stderr,
          "usage:    %s <url> [<method-name>] [<arg1>,...]\n"
          "  sample: %s http://localhost:20000/RPC2 bc 1 2\n",
          [pn cString], [pn cString]);
}

- (int)run {
  int  exitCode  = 0;
  int  loopCount = 0;
  id   result;

  if (self->client == nil) {
    NSLog(@"missing XML-RPC client object ...");
    return EXIT_FAIL;
  }
  
  do {
    result = [self->client
                  invokeMethodNamed:self->methodName
                  parameters:self->parameters];
    loopCount++;
  }
  while ((result == nil) && loopCount < 20);

  if (result == nil) {
    NSLog(@"call failed, no result (looped %i times) ?!", loopCount);
    return EXIT_FAIL;
  }
  
  [self printElement:result];
  
  if ([result isKindOfClass:[NSException class]]) {
    exitCode = 255;
  }

  return exitCode;
}

@end /* XmlRpcClient */

int main(int argc, char **argv, char **env) {
  NSAutoreleasePool *pool;
  XmlRpcClient      *client;
  NSArray           *arguments;
  int               exitCode;
  
  pool = [[NSAutoreleasePool alloc] init];
#if LIB_FOUNDATION_LIBRARY || defined(GS_PASS_ARGUMENTS)
  [NSProcessInfo initializeWithArguments:argv count:argc environment:env];
#endif
  
  /* our sockets need to know, if the PIPE is broken */
  signal(SIGPIPE, SIG_IGN);

  arguments = [[NSProcessInfo processInfo] argumentsWithoutDefaults];
  if ((client = [[XmlRpcClient alloc] initWithArguments:arguments]) == nil)
    exitCode = 2;
  else
    exitCode =  [client run];
  
  [client release];
  [pool   release];
  
  exit(exitCode);
  return exitCode;
}
