/*
  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.
*/

#include "NGHttp+WO.h"
#include <NGHttp/NGHttp.h>
#include <NGObjWeb/WOCookie.h>
#include <NGObjWeb/WORequest.h>
#include <NGMime/NGMime.h>
#include "common.h"

@interface WORequest(NGSupport)
- (void)_setHttpRequest:(NGHttpRequest *)_request;
@end

@implementation NGHttpRequest(WOSupport)

static Class NSArrayClass = Nil;

- (id)initWithWORequest:(WORequest *)_request {
  NGHashMap *hm;
  
  hm = [NGHashMap hashMapWithDictionary:[_request headers]];
    
  self = [self initWithMethod:[_request method]
               uri:[_request uri]
               header:hm
               version:[_request httpVersion]];
  [self setBody:[_request content]];
  
  /* transfer cookies */
  if ([[_request cookies] count] > 0)
    NSLog(@"WARNING: cannot transfer cookies to NGHttpRequest yet !");

  /* transfer headers !!! */
  
  return self;
}

- (WORequest *)woRequest {
  NSAutoreleasePool *pool;
  WORequest    *request;
  NSDictionary *woHeaders;

  pool = [[NSAutoreleasePool alloc] init];
  
  woHeaders = nil;
  
  request = [[WORequest alloc]
                        initWithMethod:[self methodName]
                        uri:[self uri]
                        httpVersion:[self httpVersion]
                        headers:woHeaders
                        content:[self woContent]
                        userInfo:nil];
  request = [request autorelease];
  
  [request _setHttpRequest:self];
  
  /* process cookies */
  {
    NSEnumerator *cs;
    WOCookie *cookie;
    
    cs = [[self woCookies] objectEnumerator];
    while ((cookie = [cs nextObject]))
      [request addCookie:cookie];
  }
  
  /* process headers */
  {
    NSEnumerator *keys;
    NSString *key;

    if (NSArrayClass == Nil)
      NSArrayClass = [NSArray class];
    
    keys = [self headerFieldNames];
    while ((key = [keys nextObject])) {
      NSEnumerator *values;
      id value;
      
      values = [self valuesOfHeaderFieldWithName:key];
      while ((value = [values nextObject])) {
        if ([value isKindOfClass:NSArrayClass]) {
          NSEnumerator *ev2;

          ev2 = [value objectEnumerator];
          while ((value = [ev2 nextObject])) {
            value = [value stringValue];
            [request appendHeader:value forKey:key];
          }
        }
        else {
          value = [value stringValue];
          [request appendHeader:value forKey:key];
        }
      }
    }
  }
  
  request = [request retain];
  [pool release]; pool = nil;
  
  return [request autorelease];
}

/* headers */

- (NSArray *)woHeaderKeys {
  NSMutableArray *keys;
  NSEnumerator   *ekeys;
  NSString       *key   = nil;
  
  keys  = [[NSMutableArray alloc] init];
  ekeys = [self headerFieldNames];
  
  while ((key = [ekeys nextObject]))
    [keys addObject:key];
  
  return [keys autorelease];
}

- (NSString *)woHeaderForKey:(NSString *)_key {
  return [[[self valuesOfHeaderFieldWithName:_key]
                 nextObject]
                 stringValue];
}

- (NSArray *)woHeadersForKey:(NSString *)_key {
  NSMutableArray *vals;
  NSEnumerator   *evals;
  NSString       *value = nil;

  vals  = [NSMutableArray arrayWithCapacity:2];
  evals = [self valuesOfHeaderFieldWithName:_key];
  
  while ((value = [evals nextObject])) {
    if ((value = [value stringValue]))
      [vals addObject:value];
  }

  return vals;
}

/* cookies */

- (void)_addHTTPCookie:(id)_cookie to:(NSMutableArray *)_a {
  static Class NGHttpCookieClass = Nil;
  static Class WOCookieClass = Nil;
  WOCookie *cookie;
  id cookieValue;
  
  if (_cookie == nil)
    return;
  
  if (NGHttpCookieClass == Nil) NGHttpCookieClass = [NGHttpCookie class];
  if (WOCookieClass     == Nil) WOCookieClass = NSClassFromString(@"WOCookie");

  if (![_cookie isKindOfClass:NGHttpCookieClass]) {
    static NGHttpCookieFieldParser *cookieParser = nil;
          
    if (cookieParser == nil)
      cookieParser = [[NGHttpCookieFieldParser alloc] init];
          
    _cookie = [_cookie stringValue];
    _cookie = [_cookie dataUsingEncoding:NSISOLatin1StringEncoding];
    _cookie = [cookieParser parseValue:_cookie ofHeaderField:@"cookie"];
  }
  
  if ([_cookie isKindOfClass:[NSArray class]]) {
    id singleCookie;
      
    _cookie = [_cookie objectEnumerator];
    
    while ((singleCookie = [_cookie nextObject]))
      [self _addHTTPCookie:singleCookie to:_a];
    return;
  }

    
    cookieValue = [_cookie value];
    if ([cookieValue isKindOfClass:[NSArray class]]) {
      if ([cookieValue count] == 0)
        cookieValue = @"";
      else if ([cookieValue count] == 1)
        cookieValue = [[cookieValue objectAtIndex:0] stringValue];
      else {
        [self logWithFormat:
                @"got %i values for cookie '%@', using first only.",
                [cookieValue count],
                [_cookie cookieName]];
        cookieValue = [[cookieValue objectAtIndex:0] stringValue];
      }
    }
    else
      cookieValue = [cookieValue stringValue];
    
    cookie = [WOCookieClass cookieWithName:[_cookie cookieName]
                            value:cookieValue
                            path:[_cookie path]
                            domain:[_cookie domainName]
                            expires:[_cookie expireDate]
                            isSecure:[_cookie needsSecureChannel]];
  
    /* WOMessage */
    if (cookie)
      [_a addObject:cookie];
}

- (NSArray *)woCookies {
  NSMutableArray *womCookies;
  NSArray        *woCookies;
  NSEnumerator   *mcookies;
  id             cookie;

  if (NSArrayClass == Nil) NSArrayClass = [NSArray class];
  
  womCookies = [NSMutableArray arrayWithCapacity:8];
  
  mcookies = [self valuesOfHeaderFieldWithName:@"cookie"];
  while ((cookie = [mcookies nextObject])) {
    if ([cookie isKindOfClass:NSArrayClass]) {
      id singleCookie;
      
      cookie = [cookie objectEnumerator];
      
      while ((singleCookie = [cookie nextObject]))
        [self _addHTTPCookie:singleCookie to:womCookies];
      
      continue;
    }
    
    [self _addHTTPCookie:cookie to:womCookies];
  }
  
  woCookies = [womCookies copy];
  return [woCookies autorelease];
}

/* content */

- (NSData *)woContent {
  NGMimePartGenerator *gen;
  id content;
  
  if ((content = [self body]) == nil) {
    /* no body */
    return nil;
  }
  
  if ([content isKindOfClass:[NSData class]])
    return content;
  
  if (![content conformsToProtocol:@protocol(NGMimePart)])
    return [[content stringValue] dataUsingEncoding:NSASCIIStringEncoding];
  
  gen  = [[NGMimePartGenerator alloc] init];
  content = [gen generateMimeFromPart:content];
  [gen release];
  return content;
}

/* form parameters */

static NGMimeType *multipartFormData = nil;
static Class      DispClass = Nil;

- (id)_decodeMultiPartFormDataContent {
  NGMutableHashMap    *formContent;
  NGMimeMultipartBody *ebody;
  NSArray             *parts;
  unsigned            i, count;

  if (DispClass == Nil)
    DispClass = [NGMimeContentDispositionHeaderField class];
  
  ebody = [self body];
  if (![ebody isKindOfClass:[NGMimeMultipartBody class]]) {
    [self logWithFormat:
	    @"ERROR: form-data parser expected MultipartBody, got %@", ebody];
    return [[NGHashMap alloc] init];
  }
  
  parts = [ebody parts];
  count = [parts count];
  
  [self debugWithFormat:@"%s:   %i parts %@", __PRETTY_FUNCTION__, 
          count, parts];
  
  formContent = [[NGMutableHashMap alloc] init];
  for (i = 0; i < count; i++) {
    NGMimeContentDispositionHeaderField *disposition;
    id<NGMimePart> bodyPart;
    NSString *name;
    id       partBody;
	  
    bodyPart = [parts objectAtIndex:i];
    disposition =
      [[bodyPart valuesOfHeaderFieldWithName:@"content-disposition"]nextObject];
          
    if (disposition == nil) {
      [self logWithFormat:
	      @"ERROR: did not find content disposition in form part %@",
	      bodyPart];
      continue;
    }

    /* morph to disposition field in case it's unparsed ... */
    if (![disposition isKindOfClass:DispClass]) {
              disposition =
                [[DispClass alloc] initWithString:[disposition stringValue]];
              [disposition autorelease];
    }
            
    name     = [disposition name];
    partBody = [bodyPart body];
    
    if (partBody)
      [(NGMutableHashMap *)formContent addObject:partBody forKey:name];
  }
  return formContent;
}

- (NGHashMap *)_decodeFormContentURLParameters:(id)formContent {
  /* all this sounds expensive ;-) */
  NSString   *s;
  unsigned   urilen;
  char       *uribuf;
  const char *p = uribuf;
  NGHashMap  *map;
  
  if ((s = [self uri]) == nil)
    return formContent;
  if ([s rangeOfString:@"?"].length == 0)
    return formContent;
  
  urilen = [s cStringLength];
  p = uribuf = malloc(urilen + 4);
  [s getCString:uribuf]; // UNICODE?
  
  if ((p = index(p, '?')) == NULL) {
    if (uribuf) free(uribuf);
    return formContent;
  }
  
  p++; // skip the '?'
  map = NGDecodeUrlFormParameters(p, strlen(p));
  if (uribuf) free(uribuf); uribuf = NULL; p = NULL;
  
  if (map == nil) 
    return formContent;
  if (formContent == nil)
    return map;
  
  map = [map autorelease]; // NGDecodeUrlFormParameters returns a retained map!
  
  if ([formContent isKindOfClass:[NGHashMap class]]) {
    NSEnumerator *keys;
    id key, tmp;
  
    tmp = formContent;
    formContent =  [[NGMutableHashMap alloc] initWithHashMap:tmp];
    [tmp release];
  
    keys = [map keyEnumerator];
    while ((key = [keys nextObject])) {
      NSEnumerator *values;
      id value;
            
      values = [map objectEnumeratorForKey:key];
      while ((value = [values nextObject]))
	[formContent addObject:value forKey:key];
    }
  }
  else if ([formContent isKindOfClass:[NSDictionary class]]) {
    NSEnumerator *keys;
    id key, tmp;
	  
    tmp = formContent;
    formContent = [[NGMutableHashMap alloc] initWithDictionary:tmp];
    [tmp release];
  
    keys = [map keyEnumerator];
    while ((key = [keys nextObject])) {
      NSEnumerator *values;
      id value;
  
      values = [map objectEnumeratorForKey:key];
      while ((value = [values nextObject]))
	[formContent addObject:value forKey:key];
    }
  }
  return formContent;
}

- (NGHashMap *)formParameters {
  id formContent;
  
  if (multipartFormData == nil) {
    multipartFormData = [NGMimeType mimeType:@"multipart/form-data"];
    multipartFormData = [multipartFormData retain];
  }
  
  if ([[self methodName] isEqualToString:@"POST"]) {
    NGMimeType *contentType = [self contentType];
    
    //NSLog(@"%s: process POST, ctype %@", __PRETTY_FUNCTION__, contentType);
    
    formContent = [contentType hasSameType:multipartFormData]
      ? [self _decodeMultiPartFormDataContent]
      : [[self body] retain];
  }
  else
    formContent = nil;
  
  /* decode URL parameters */
  formContent = [self _decodeFormContentURLParameters:formContent];
  
  return [formContent autorelease];
}

@end /* NGHttpRequest */
