/*
  Copyright (C) 2002-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 "SoObject.h"
#include "SoClassRegistry.h"
#include "SoClass.h"
#include "SoSecurityManager.h"
#include "WOContext+SoObjects.h"
#include <EOControl/EOClassDescription.h>
#include <NGObjWeb/WOApplication.h>
#include <NGObjWeb/WORequest.h>
#include "common.h"

@interface NSObject(Folders)
- (BOOL)isFolderish;
@end

@implementation NSObject(SoObject)

static int debugLookup  = -1;
static int debugBaseURL = -1;
static void _initialize(void) {
  if (debugLookup == -1) {
    debugLookup = [[NSUserDefaults standardUserDefaults]
		                   boolForKey:@"SoDebugKeyLookup"] ? 1 : 0;
  }
  if (debugBaseURL == -1) {
    debugBaseURL = [[NSUserDefaults standardUserDefaults]
		                     boolForKey:@"SoDebugBaseURL"] ? 1 : 0;
  }
}

/* classes */

+ (SoClass *)soClass {
  static SoClassRegistry *registry = nil; // THREAD
  if (registry == nil)
    registry = [[SoClassRegistry sharedClassRegistry] retain];
  return [registry soClassForClass:self];
}
- (SoClass *)soClass {
  return [[self class] soClass];
}
- (NSString *)soClassName {
  return [[self soClass] className];
}

+ (SoClassSecurityInfo *)soClassSecurityInfo {
  return [[self soClass] soClassSecurityInfo];
}

- (NSClassDescription *)soClassDescription {
  return [[self soClass] soClassDescription];
}

/* invocation */

- (BOOL)isCallable {
  return NO;
}
- (id)clientObject {
  return self;
}

- (id)callOnObject:(id)_client inContext:(id)_ctx {
  return nil;
}

- (NSString *)defaultMethodNameInContext:(id)_ctx {
  return @"index";
}
- (id)lookupDefaultMethod {
  id ctx = nil;
  
  // TODO: lookupDefaultMethod should be rewritten to take a context!
  ctx = [[WOApplication application] context];

  // TODO: we might want to return a redirect?!
  
  return [self lookupName:[self defaultMethodNameInContext:ctx]
	       inContext:ctx
	       acquire:YES];
}

/* keys */

- (BOOL)hasName:(NSString *)_key inContext:(id)_ctx {
  /* this corresponds to Zope's/Pythons __hasattr__() */
  if ([[self soClass] hasKey:_key inContext:_ctx])
    return YES;
  if ([[self toOneRelationshipKeys] containsObject:_key])
    return YES;
  return NO;
}

- (id)lookupName:(NSString *)_key inContext:(id)_ctx acquire:(BOOL)_flag {
  /* this corresponds to Zope's/Pythons __getattr__() */
  id value;
  _initialize();
  
  if (debugLookup)
    [self debugWithFormat:@"lookup key '%@'", _key];
  
  /* we might want to cache class methods ? */
  if ((value = [[self soClass] lookupKey:_key inContext:_ctx]) == nil) {
    if (debugLookup) {
      [self logWithFormat:@"  did not find key '%@' in SoClass: %@", 
              _key, [self soClass]];
    }
    
    if ([[self toOneRelationshipKeys] containsObject:_key]) {
      if (debugLookup) {
	[self logWithFormat:
                @"  %@ is a toOneRelationshipKey (use -valueForKey:)", _key];
      }
      value = [self valueForKey:_key];
    }
  }
  
  if (value) {
    if ((value = [value bindToObject:self inContext:_ctx]) == nil) {
      if (debugLookup)
        [self logWithFormat:@"  value from class did not bind: %@", 
	      [self soClass]];
      return nil;
    }
  }
  else if (_flag) { /* try to acquire from container */
    if (debugLookup)
      [self logWithFormat:@"  try to acquire %@ from container ...", _key];
    value = [[self container] lookupName:_key inContext:_ctx acquire:YES];
  }
  
  if (debugLookup) [self logWithFormat:@"  looked up value: %@", value];
  return value;
}

- (NSException *)validateName:(NSString *)_key inContext:(id)_ctx {
  static SoSecurityManager *sm = nil;
  if (sm == nil) sm = [[SoSecurityManager sharedSecurityManager] retain];
  return [sm validateName:_key ofObject:self inContext:_ctx];
}

/* binding */

- (id)bindToObject:(id)_object inContext:(id)_ctx {
  return self;
}

/* security */

- (NSString *)ownerInContext:(id)_ctx {
  /* objects are not owned by default, suggest to inherit owner */
  return [[self container] ownerInContext:_ctx];
}
- (id)authenticatorInContext:(id)_ctx {
  return [[_ctx application] authenticatorInContext:_ctx];
}

/* containment */

- (id)container {
  return nil;
}
- (void)detachFromContainer {
}
- (NSString *)nameInContainer {
  return nil;
}

- (NSArray *)objectContainmentStack {
  NSMutableArray *ma;
  id object;
  
  if ((object = [self container]) == nil)
    /* this is root */
    return [NSArray arrayWithObject:self];
  
  ma = [[NSMutableArray alloc] initWithCapacity:16];
  for (object = self; object; object = [object container])
    [ma insertObject:(object ? object : (id)[NSNull null]) atIndex:0];

  object = [ma shallowCopy];
  [ma release];
  return [object autorelease];
}

- (NSArray *)reversedPathArrayToSoObject {
  NSMutableArray *ma;
  id object, nextObject;
  
  if ((object = [self container]) == nil)
    /* this is root */
    return [NSArray array];
  
  ma = [NSMutableArray arrayWithCapacity:16];
  for (object = self; (nextObject = [object container]); object = nextObject) {
    NSString *oname;
    
    oname = [object nameInContainer];
    [ma addObject:(oname ? oname : (id)[NSNull null])];
  }
  return ma;
}
- (NSArray *)pathArrayToSoObject {
  NSArray      *pathArray;
  NSEnumerator *e;
  
  if ((pathArray = [self reversedPathArrayToSoObject]) == nil)
    return nil;
  
  e = [pathArray reverseObjectEnumerator];
  pathArray = [[[NSArray alloc] initWithObjectsFromEnumerator:e] autorelease];
  return pathArray;
}

- (NSString *)baseURLInContext:(id)_ctx {
  NSString *baseURL;
  id parent;
  _initialize();
  
  // TODO: should we check the traversal path?
  
  if ((parent = [self container]) != nil) {
    /* Note: cannot use -stringByAppendingPathComponent: on OSX! */
    NSString *name;
    
    if (parent == self) {
      [self logWithFormat:
	      @"WARNING: container==object in baseURL calculation (loop?): %@",
	      self];
    }
    
    baseURL = [parent baseURLInContext:_ctx];
    if (![baseURL hasSuffix:@"/"])
      baseURL = [baseURL stringByAppendingString:@"/"];
    
    name    = [[self nameInContainer] stringByEscapingURL];
    baseURL = [baseURL stringByAppendingString:name];
    
    if (debugBaseURL) {
      [self logWithFormat:@"baseURL(%@,%@): %@", 
              [self nameInContainer], [[self container] baseURL], baseURL];
    }
  }
  else {
    baseURL = [self rootURLInContext:_ctx];
    if (debugBaseURL) {
      [self logWithFormat:@"ROOT baseURL(no container, name=%@): %@", 
              [self nameInContainer], baseURL];
    }
  }
  
  /* add a trailing slash for folders */
  
  if (![baseURL hasSuffix:@"/"]) {
    if ([self respondsToSelector:@selector(isFolderish)]) {
      if ([self isFolderish])
	baseURL = [baseURL stringByAppendingString:@"/"];
    }
  }
  
  return baseURL;
}
- (NSString *)rootURLInContext:(id)_ctx {
  NSMutableString *ms;
  BOOL      isHTTPS = NO; // TODO: what about https??
  NSString  *rootURL;
  WORequest *rq;
  NSString  *rh, *tmp;
  int       port;
  _initialize();
  
  if ((rootURL = [_ctx rootURL]) != nil) {
    if (debugBaseURL) {
      [self logWithFormat:@"  using root-url from context: %@",
              rootURL];
    }
    return rootURL;
  }

  // TODO: this is somewhat weird, why don't we use WOContext for URL gen.?
  
  rq   = [(WOContext *)_ctx request];
  port = [[rq headerForKey:@"x-webobjects-server-port"] intValue];
  
  /* TODO: how to handle Evolution bug which sends invalid port ? */
  if (port == 0) {
    static BOOL didWarn = NO;
    if (!didWarn) {
      [self logWithFormat:
	      @"WARNING(%s:%i): got an empty port, probably buggy "
	      @"SOUP host header!",
	      __PRETTY_FUNCTION__, __LINE__];
      didWarn = YES;
    }
    port = 23000;
  }

  ms = [[NSMutableString alloc] initWithCapacity:128];
  
  if ((tmp = [rq headerForKey:@"host"])) { 
    /* check whether we have a host header with port */
    if ([tmp rangeOfString:@":"].length == 0)
      tmp = nil;
  }

  if (tmp) {
    isHTTPS = 
      [[rq headerForKey:@"x-webobjects-server-url"] hasPrefix:@"https"];
    [ms appendString:isHTTPS ? @"https://" : @"http://"]; 
    [ms appendString:tmp];
  }
  else if ((tmp = [rq headerForKey:@"x-webobjects-server-url"])) {
    /* sometimes the URL is just wrong! (suggests port 80) */
    if ([tmp hasSuffix:@":0"] && [tmp length] > 2) // TODO: bad bad bad
      tmp = [tmp substringToIndex:([tmp length] - 2)];
    [ms appendString:tmp];
  }
  else {
    [ms appendString:isHTTPS ? @"https://" : @"http://"]; 
  
    [ms appendString:[rq headerForKey:@"x-webobjects-server-name"]];
    if ((isHTTPS ? (port != 443) : (port != 80)) && port != 0)
      [ms appendFormat:@":%i", port];
  }
  if (![ms hasSuffix:@"/"]) [ms appendString:@"/"];
  
  /* appname, two cases: */
  /*   a) direct access,  eg /MyFolder */
  /*   b) access via app, eg /MyApp/so/MyFolder */
  [ms appendString:[rq applicationName]];
  if (![ms hasSuffix:@"/"]) [ms appendString:@"/"];
  
  /* done */
  rootURL = [[ms copy] autorelease];
  [ms release];
  if (debugBaseURL)
    [self logWithFormat:@"  constructed root-url: %@", rootURL];
  
  /* some hack for the request handler? */
  rh = [rq requestHandlerKey];
  if ([[[_ctx application] registeredRequestHandlerKeys] containsObject:rh])
    rootURL = [rootURL stringByAppendingFormat:@"%@/", rh];
  
  if (debugBaseURL) {
    [self logWithFormat:@"  setting root-url in context: %@",
	    rootURL];
  }
  [(WOContext *)_ctx setRootURL:rootURL];
  return rootURL;
}

- (NSString *)baseURL {
  /* you should use the context method ! */
  return [self baseURLInContext:[[WOApplication application] context]];
}

@end /* NSObject(SoObject) */

@implementation WOApplication(Authenticator)

- (NSString *)ownerInContext:(id)_ctx {
  /* objects are not owned by default */
  return nil;
}
- (id)authenticatorInContext:(id)_ctx {
  return nil;
}

@end /* WOApplication(Authenticator) */
