/*
  Copyright (C) 2004-2005 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.
*/

#include <NGObjWeb/SoApplication.h>

// @interface classtree : NSObject
// {
//   Class topClass;
//   int indentLevel;
// }

// - (id) initWithTopClass: (Class) newTopClass;
// - (void) dumpSiblings: (Class) node;

// @end

// @implementation classtree

// + (id) newWithTopClass: (Class) newTopClass
// {
//   id newTree;

//   newTree = [[self alloc] initWithTopClass: newTopClass];
//   [newTree autorelease];

//   return newTree;
// }

// - (id) initWithTopClass: (Class) newTopClass
// {
//   if ((self = [self init]))
//     topClass = newTopClass;

//   return self;
// }

// #define indentGap 2

// - (NSString *) indentSpaces
// {
//   char *spaceString;

//   spaceString = malloc(sizeof (char *) * indentGap * indentLevel + 1);
//   *(spaceString + indentGap * indentLevel) = 0;
//   memset (spaceString, ' ', indentGap * indentLevel);

//   return [[NSString alloc] initWithCStringNoCopy: spaceString
//                            length: indentGap * indentLevel
//                            freeWhenDone: YES];
// }

// - (void) dumpNode: (Class) node
// {
//   Class currentSubclass;
//   unsigned int count;

//   count = 0;
//   currentSubclass = node->subclass_list;
//   if (currentSubclass)
//     {
//       NSLog(@"%@%@:",
//             [[self indentSpaces] autorelease], NSStringFromClass(node));
//       indentLevel++;
//       [self dumpSiblings: currentSubclass];
//       indentLevel--;
//     }
//   else
//     NSLog(@"%@%@", [self indentSpaces], NSStringFromClass(node));
// }

// - (void) dumpSiblings: (Class) node
// {
//   Class currentNode;

//   currentNode = node;
//   while (currentNode && currentNode->instance_size)
//     {
//       [self dumpNode: currentNode];
//       currentNode = currentNode->sibling_class;
//     }
// }

// - (void) dumpTree
// {
//   indentLevel = 0;

//   [self dumpSiblings: topClass];
// }

// @end

@interface SOGo : SoApplication
{
    NSMutableDictionary *localeLUT;
}

- (NSDictionary *) currentLocaleConsideringLanguages:(NSArray *)_langs;
- (NSDictionary *) localeForLanguageNamed:(NSString *)_name;

@end

#include "SOGoProductLoader.h"
#include <WEExtensions/WEResourceManager.h>
#include <SOGo/SOGoAuthenticator.h>
#include <SOGo/SOGoUserFolder.h>
#include <SOGo/SOGoPermissions.h>
#include "common.h"

@implementation SOGo

static unsigned int vMemSizeLimit = 0;
static BOOL doCrashOnSessionCreate = NO;

+ (void)initialize {
  NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
  SoClassSecurityInfo *sInfo;
  NSArray *basicRoles;
  id tmp;
  
  doCrashOnSessionCreate = [ud boolForKey:@"SOGoCrashOnSessionCreate"];

  /* vMem size check - default is 200MB */
    
  tmp = [ud objectForKey:@"SxVMemLimit"];
  vMemSizeLimit = (tmp != nil)
    ? [tmp intValue]
    : 200;
  if (vMemSizeLimit > 0) {
    NSLog(@"Note: vmem size check enabled: shutting down app when "
	  @"vmem > %d MB", vMemSizeLimit);
  }
#if LIB_FOUNDATION_LIBRARY
  if ([ud boolForKey:@"SOGoEnableDoubleReleaseCheck"])
    [NSAutoreleasePool enableDoubleReleaseCheck:YES];
#endif

  /* SoClass security declarations */
  sInfo = [self soClassSecurityInfo];
  /* require View permission to access the root (bound to authenticated ...) */
  [sInfo declareObjectProtected: SoPerm_View];

  /* to allow public access to all contained objects (subkeys) */
  [sInfo setDefaultAccess: @"allow"];

  basicRoles = [NSArray arrayWithObjects: SoRole_Authenticated,
                        SOGoRole_FreeBusy, nil];

  /* require Authenticated role for View and WebDAV */
  [sInfo declareRoles: basicRoles asDefaultForPermission: SoPerm_View];
  [sInfo declareRoles: basicRoles asDefaultForPermission: SoPerm_WebDAVAccess];
}

- (id)init {
  if ((self = [super init])) {
    WOResourceManager *rm;
    
    /* ensure core SoClass'es are setup */
    [$(@"SOGoObject")        soClass];
    [$(@"SOGoContentObject") soClass];
    [$(@"SOGoFolder")        soClass];
    
    /* setup locale cache */
    self->localeLUT = [[NSMutableDictionary alloc] initWithCapacity:2];
    
    /* load products */
    [[SOGoProductLoader productLoader] loadProducts];
    
    /* setup resource manager */
    rm = [[WEResourceManager alloc] init];
    [self setResourceManager:rm];
  }
  return self;
}

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

/* authenticator */

- (id)authenticatorInContext:(id)_ctx {
  return [$(@"SOGoAuthenticator") sharedSOGoAuthenticator];
}

/* name lookup */

- (BOOL)isUserName:(NSString *)_key inContext:(id)_ctx {
  if ([_key length] < 1)
    return NO;
  
  if (isdigit([_key characterAtIndex:0]))
    return NO;

  return YES;
}

- (id)lookupUser:(NSString *)_key inContext:(id)_ctx {
  return [[[$(@"SOGoUserFolder") alloc] 
	    initWithName:_key inContainer:self] autorelease];
}

- (void)_setupLocaleInContext:(WOContext *)_ctx {
  NSArray      *langs;
  NSDictionary *locale;
  
  if ([[_ctx valueForKey:@"locale"] isNotNull])
    return;

  langs = [[(WOContext *)_ctx request] browserLanguages];
  locale = [self currentLocaleConsideringLanguages:langs];
  [_ctx takeValue:locale forKey:@"locale"];
}

- (id)lookupName:(NSString *)_key inContext:(id)_ctx acquire:(BOOL)_flag {
  id obj;

  /* put locale info into the context in case it's not there */
  [self _setupLocaleInContext:_ctx];
  
  /* first check attributes directly bound to the application */
  if ((obj = [super lookupName:_key inContext:_ctx acquire:_flag]))
    return obj;
  
  /* 
     The problem is, that at this point we still get request for resources,
     eg 'favicon.ico'.
     
     Addition: we also get queries for various other methods, like "GET" if
               no method was provided in the query path.
  */
  
  if ([_key isEqualToString:@"favicon.ico"])
    return nil;

  if ([self isUserName:_key inContext:_ctx])
    return [self lookupUser:_key inContext:_ctx];
  
  return nil;
}

/* WebDAV */

- (NSString *)davDisplayName {
  /* this is used in the UI, eg in the navigation */
  return @"SOGo";
}

/* exception handling */

- (WOResponse *)handleException:(NSException *)_exc
  inContext:(WOContext *)_ctx
{
  printf("EXCEPTION: %s\n", [[_exc description] cString]);
  abort();
}

/* runtime maintenance */

// - (void) _dumpClassAllocation
// {
//   classtree *ct;

//   ct = [classtree newWithTopClass: [NSObject class]];
//   [ct dumpTree];
// }

- (void)checkIfDaemonHasToBeShutdown {
  unsigned int limit, vmem;
  
  if ((limit = vMemSizeLimit) == 0)
    return;

  vmem = [[NSProcessInfo processInfo] virtualMemorySize]/1048576;

  if (vmem > limit) {
    [self logWithFormat:
          @"terminating app, vMem size limit (%d MB) has been reached"
          @" (currently %d MB)",
          limit, vmem];
//     [self _dumpClassAllocation];
    [self terminate];
  }
}

- (WOResponse *)dispatchRequest:(WORequest *)_request {
  static NSArray *runLoopModes = nil;
  WOResponse *resp;

  resp = [super dispatchRequest:_request];

  if ([self isTerminating])
    return resp;

  if (runLoopModes == nil)
    runLoopModes = [[NSArray alloc] initWithObjects:NSDefaultRunLoopMode, nil];
  
  // TODO: a bit complicated? (-perform:afterDelay: doesn't work?)
  [[NSRunLoop currentRunLoop] performSelector:
				@selector(checkIfDaemonHasToBeShutdown)
			      target:self argument:nil
			      order:1 modes:runLoopModes];
  return resp;
}

/* session management */

- (id)createSessionForRequest:(WORequest *)_request {
  [self warnWithFormat:@"session creation requested!"];
  if (doCrashOnSessionCreate)
    abort();
  return [super createSessionForRequest:_request];
}

/* localization */

- (NSDictionary *)currentLocaleConsideringLanguages:(NSArray *)_langs {
  unsigned i, count;

  /* assume _langs is ordered by priority */
  count = [_langs count];
  for (i = 0; i < count; i++) {
    NSString     *lname;
    NSDictionary *locale;
    
    lname  = [_langs objectAtIndex:i];
    locale = [self localeForLanguageNamed:lname];
    if (locale != nil)
      return locale;
  }
  /* no appropriate language, fallback to default */
  return [self localeForLanguageNamed:@"English"];
}

- (NSString *)pathToLocaleForLanguageNamed:(NSString *)_name {
  static Class MainProduct = Nil;
  NSString *lpath;

  lpath = [[self resourceManager] pathForResourceNamed:@"Locale"
				  inFramework:nil
				  languages:[NSArray arrayWithObject:_name]];
  if ([lpath isNotNull])
    return lpath;
  
  if (MainProduct == Nil) {
    if ((MainProduct = $(@"MainUIProduct")) == Nil)
      [self errorWithFormat:@"did not find MainUIProduct class!"];
  }
  
  lpath = [(id)MainProduct pathToLocaleForLanguageNamed:_name];
  if ([lpath isNotNull])
    return lpath;
  
  return nil;
}

- (NSDictionary *)localeForLanguageNamed:(NSString *)_name {
  NSString     *lpath;
  id           data;
  NSDictionary *locale;
  
  if (![_name isNotNull]) {
    [self errorWithFormat:@"%s: name parameter must not be nil!",
	  __PRETTY_FUNCTION__];
    return nil;
  }
  
  if ((locale = [self->localeLUT objectForKey:_name]) != nil)
    return locale;
  
  if ((lpath = [self pathToLocaleForLanguageNamed:_name]) == nil) {
    [self errorWithFormat:@"did not find Locale for language: %@", _name];
    return nil;
  }
  
  if ((data = [NSData dataWithContentsOfFile:lpath]) == nil) {
    [self logWithFormat:@"%s didn't find locale with name:%@",
	  __PRETTY_FUNCTION__,
	  _name];
    return nil;
  }
  data = [[[NSString alloc] initWithData:data
                            encoding:NSUTF8StringEncoding] autorelease];
  locale = [data propertyList];
  if (locale == nil) {
    [self logWithFormat:@"%s couldn't load locale with name:%@",
	  __PRETTY_FUNCTION__,
	  _name];
    return nil;
  }
  [self->localeLUT setObject:locale forKey:_name];
  return locale;
}

/* name (used by the WEResourceManager) */

- (NSString *)name {
  return @"SOGo-0.9";
}

@end /* SOGo */
