/*
  Copyright (C) 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 "WEResourceManager.h"
#include "WEStringTableManager.h"
#include "WEResourceKey.h"
#include "common.h"

@implementation WEResourceManager

static BOOL          debugOn         = NO;
static BOOL          debugComponents = NO;
static NSArray       *wsPathes       = nil;
static NSArray       *templatePathes = nil;
static NSString      *suffix   = nil;
static NSString      *prefix   = nil;
static NSFileManager *fm   = nil;
static NSNull        *null = nil;
static NSString *themesDirName = @"Themes";

+ (NSString *)shareSubpath {
  static NSString *shareSubPath = nil;
  NSString *p;
  
  if (shareSubPath != nil)
    return shareSubPath;
  
  p = [[WOApplication application] shareDirectoryName];
  p = [@"share/" stringByAppendingString:p];
  p = [p stringByAppendingString:@"/"];
  shareSubPath = [p copy];
  return shareSubPath;
}

+ (NSString *)gsTemplatesSubpath {
  NSString *p;
  
  p = [[WOApplication application] gsTemplatesDirectoryName];
  p = [@"Library/" stringByAppendingString:p];
  return p;
}
+ (NSString *)gsWebSubpath {
  NSString *p;
  
  p = [[WOApplication application] gsWebDirectoryName];
  p = [@"Library/" stringByAppendingString:p];
  return p;
}

/* locate resource directories */

+ (NSArray *)rootPathesInGNUstep {
  NSDictionary *env;
  id tmp;
  
  env = [[NSProcessInfo processInfo] environment];
  if ((tmp = [env objectForKey:@"GNUSTEP_PATHPREFIX_LIST"]) == nil)
    tmp = [env objectForKey:@"GNUSTEP_PATHLIST"];
  
  return [tmp componentsSeparatedByString:@":"];
}
+ (NSArray *)rootPathesInFHS {
  return [NSArray arrayWithObjects:@"/usr/local/", @"/usr/", nil];
}

+ (NSArray *)findResourceDirectoryPathesWithName:(NSString *)_name
  fhsName:(NSString *)_fhs
{
  NSEnumerator   *e;
  NSFileManager  *fm;
  NSMutableArray *ma;
  BOOL           isDir;
  id tmp;

  fm  = [NSFileManager defaultManager];
  ma  = [NSMutableArray arrayWithCapacity:8];
  
  e = [[self rootPathesInGNUstep] objectEnumerator];
  while ((tmp = [e nextObject]) != nil) {
    if (![tmp hasSuffix:@"/"])
      tmp = [tmp stringByAppendingString:@"/"];
    
    tmp = [tmp stringByAppendingString:_name];
    if ([ma containsObject:tmp]) continue;
    
    if (debugOn) [self logWithFormat:@"CHECK: %@", tmp];
    if (![fm fileExistsAtPath:tmp isDirectory:&isDir])
      continue;
      
    if (!isDir) continue;
    
    [ma addObject:tmp];
  }

  /* hack in FHS pathes */
  
  e = [[self rootPathesInFHS] objectEnumerator];
  while ((tmp = [e nextObject]) != nil) {
    tmp = [tmp stringByAppendingString:[[self class] shareSubpath]];
    tmp = [tmp stringByAppendingString:_fhs];
    if ([ma containsObject:tmp]) continue;
    
    if (![fm fileExistsAtPath:tmp isDirectory:&isDir])
      continue;
    if (!isDir) {
      [self logWithFormat:@"path is not a directory: %@", tmp];
      continue;
    }
    
    [ma addObject:tmp];
  }
  
  return ma;
}

+ (int)version {
  return [super version] + 0 /* v4 */;
}
+ (void)initialize {
  static BOOL isInitialized = NO;
  NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
  if (isInitialized) return;
  isInitialized = YES;
  
  NSAssert2([super version] == 4,
	    @"invalid superclass (%@) version %i !",
	    NSStringFromClass([self superclass]), [super version]);

  null = [[NSNull null] retain];
  
  if ((debugOn = [ud boolForKey:@"WEResourceManagerDebugEnabled"]))
    NSLog(@"Note: WEResourceManager debugging is enabled.");
  debugComponents = [ud boolForKey:@"WEResourceManagerComponentDebugEnabled"];
  if (debugComponents)
    NSLog(@"Note: WEResourceManager component debugging is enabled.");
  
  fm = [[NSFileManager defaultManager] retain];
  
  suffix = [[ud stringForKey:@"WOApplicationSuffix"] copy];
  prefix = [[ud stringForKey:@"WOResourcePrefix"]    copy];
  
  wsPathes = [[self findResourceDirectoryPathesWithName:
		      [self gsWebSubpath] fhsName:@"www/"] copy];
  if (debugOn)
    NSLog(@"WebServerResources pathes: %@", wsPathes);
  
  // TODO: use appname to enable different setups, maybe some var too
  templatePathes = [[self findResourceDirectoryPathesWithName:
			    [[self class] gsTemplatesSubpath]
			  fhsName:@"templates/"] copy];
  if (debugOn)
    NSLog(@"template pathes: %@", templatePathes);
  if ([templatePathes count] == 0)
    NSLog(@"Note: found no directories containing flat templates.");
}

+ (NSArray *)availableThemes {
  static NSArray *lthemes = nil;
  NSMutableSet  *themes;
  NSEnumerator  *e;
  NSFileManager *fm;
  NSString      *path;

  if (lthemes != nil)
    return lthemes;
  
  themes = [NSMutableSet setWithCapacity:16];
  fm     = [NSFileManager defaultManager];
  
  e = [templatePathes objectEnumerator];
  while ((path = [e nextObject]) != nil) {
    NSArray *dl;
    
    path = [path stringByAppendingPathComponent:themesDirName];
    dl   = [fm directoryContentsAtPath:path];
    
    [themes addObjectsFromArray:dl];
  }
  
  /* remove directories to be ignored */
  [themes removeObject:@".svn"];
  [themes removeObject:@"CVS"];
  
  lthemes = [[[themes allObjects] 
	       sortedArrayUsingSelector:@selector(compare:)] copy];
  if ([lthemes count] > 0) {
    NSLog(@"Note: located themes: %@", 
	  [lthemes componentsJoinedByString:@", "]);
  }
  else
    NSLog(@"Note: located no additional themes.");
  return lthemes;
}

- (id)initWithPath:(NSString *)_path {
  if ((self = [super initWithPath:_path])) {
    if ([WOApplication isCachingEnabled]) {
      self->keyToComponentPath =
	[[NSMutableDictionary alloc] initWithCapacity:128];
      
      self->keyToPath = [[NSMutableDictionary alloc] initWithCapacity:1024];
      self->keyToURL  = [[NSMutableDictionary alloc] initWithCapacity:1024];
    }
    else
      [self logWithFormat:@"Note: component path caching is disabled!"];
    
    self->labelManager = [[WEStringTableManager alloc] init];
    self->cachedKey    = [[WEResourceKey alloc] initCachedKey];
  }
  return self;
}
- (id)init {
  // TODO: maybe search and set some 'share/ogo' path?
  return [self initWithPath:nil];
}

- (void)dealloc {
  [self->cachedKey          release];
  [self->labelManager       release];
  [self->keyToURL           release];
  [self->keyToPath          release];
  [self->keyToComponentPath release];
  [super dealloc];
}

/* accessors */

- (NGBundleManager *)bundleManager {
  static NGBundleManager *bm = nil; // THREAD
  if (bm == nil) bm = [[NGBundleManager defaultBundleManager] retain];
  return bm;
}

/* resource cache */

static id
checkCache(NSDictionary *_cache, WEResourceKey *_key,
	   NSString *_n, NSString *_fw, NSString *_l)
{
  if (_cache == nil) {
    if (debugOn) NSLog(@"cache disabled.");
    return nil; /* caching disabled */
  }
  
  /* setup cache key (THREAD) */
  _key->hashValue     = 0; /* reset, calculate on next access */
  _key->name          = _n;
  _key->frameworkName = _fw;
  _key->language      = _l;
  
  return [_cache objectForKey:_key];
}

- (void)cacheValue:(id)_value inCache:(NSMutableDictionary *)_cache {
  WEResourceKey *k;
  
  if (_cache == nil) return; /* caching disabled */
  
  /* we need to dup, because the cachedKey does not retain! */
  k = [self->cachedKey duplicate];
  
  if (debugOn) {
    [self debugWithFormat:@"cache key %@(#%d): %@", k, [self->keyToPath count],
	    _value];
  }
  
  [_cache setObject:(_value ? _value : (id)null) forKey:k];
  [k release]; k = nil;
}

/* locate Resources */

- (NSString *)_weCheckPath:(NSString *)_p forResourceNamed:(NSString *)_name
  inFramework:(NSString *)_frameworkName
  language:(NSString *)_language
{
  NSString *path;
  
  path = [_frameworkName length] > 0
    ? [_p stringByAppendingPathComponent:_frameworkName]
    : _p;
      
  /* check language */
  if (_language != nil) {
    path = [path stringByAppendingPathComponent:_language];
    path = [path stringByAppendingPathExtension:@"lproj"];
  }
  
  path = [path stringByAppendingPathComponent:_name];
  if (debugOn) [self debugWithFormat:@"  check path: '%@'", path];
      
  if (![fm fileExistsAtPath:path])
    return nil;
  
  return path;
}

- (NSString *)_weCheckPathes:(NSArray *)_p forResourceNamed:(NSString *)_name
  inFramework:(NSString *)_frameworkName
  language:(NSString *)_language
{
  NSEnumerator *e;
  NSString     *path;
  
  e = [_p objectEnumerator];
  while ((path = [e nextObject]) != nil) {
    path = [self _weCheckPath:path forResourceNamed:_name
		 inFramework:_frameworkName language:_language];
    if (path != nil) {
      if (debugOn) [self debugWithFormat:@"FOUND: '%@'", path];
      return path;
    }
  }
  return nil;
}

- (NSString *)_wePathForResourceNamed:(NSString *)_name
  inFramework:(NSString *)_fwName
  language:(NSString *)_lang
  searchPathes:(NSArray *)_pathes
{
  // TODO: a lot of DUP code with _urlForResourceNamed, needs some refacturing
  NSString *path;
  
  if (debugOn) [self debugWithFormat:@"lookup resource '%@'", _name];
  
  /* check cache */
  
  path = checkCache(self->keyToPath, self->cachedKey, _name, _fwName, _lang);
  if (path != nil) {
    if (debugOn) [self debugWithFormat:@"  found in cache: %@", path];
    return [path isNotNull] ? path : nil;
  }
  
  /* check for framework resources (webserver resources + framework) */

  if (debugOn) 
    [self debugWithFormat:@"check framework resources ..."];
  path = [self _weCheckPathes:_pathes forResourceNamed:_name
	       inFramework:_fwName language:_lang];
  if (path != nil) {
    [self cacheValue:path inCache:self->keyToPath];
    return path;
  }
  
  /* check in basepath of webserver resources */
  
  // TODO: where is the difference, same call like above?
  if (debugOn) [self debugWithFormat:@"check global resources ..."];
  path = [self _weCheckPathes:_pathes forResourceNamed:_name
	       inFramework:_fwName language:_lang];
  if (path != nil) {
    [self cacheValue:path inCache:self->keyToPath];
    return path;
  }
  
  /* finished processing */
  if (debugOn) 
    [self debugWithFormat:@"NOT FOUND: %@ (%@)", _name, self->cachedKey];
  return nil;
}

- (BOOL)shouldLookupResourceInWebServerResources:(NSString *)_name {
  if ([_name hasSuffix:@".wox"]) return NO;
  if ([_name hasSuffix:@".wo"])  return NO;
  return YES;
}

- (NSString *)_wePathForResourceNamed:(NSString *)_name
  inFramework:(NSString *)_fwName
  language:(NSString *)_lang
{
  NSString *p;
  
  /* check in webserver resources */
  
  if ([self shouldLookupResourceInWebServerResources:_name]) {
    p = [self _wePathForResourceNamed:_name inFramework:_fwName
              language:_lang searchPathes:wsPathes];
    if (p != nil) return p;
  }
  
  return nil;
}

- (BOOL)isTemplateResourceName:(NSString *)_name {
  // TODO: non-extensible
  return [_name hasSuffix:@".wox"];
}

- (NSString *)pathForResourceNamed:(NSString *)_name
  inFramework:(NSString *)_fwName
  languages:(NSArray *)_langs
{
  /* 
     Note: this is also called by the superclass method 
           -pathToComponentNamed:inFramework: for each registered component
	   extension.
  */
  NSEnumerator *e;
  NSString     *language;
  NSString     *rpath;
  
  if ([_name length] == 0) {
    [self debugWithFormat:@"got no name for resource lookup?!"];
    return nil;
  }
  
  if (debugOn) {
    [self debugWithFormat:@"pathForResourceNamed: %@/%@ (languages: %@)",
            _name, _fwName, [_langs componentsJoinedByString:@","]];
  }

  if ([self isTemplateResourceName:_name]) {
    if (debugOn) [self debugWithFormat:@"  is template resource .."];
    return [self pathToComponentNamed:[_name stringByDeletingPathExtension]
		 inFramework:_fwName
		 languages:_langs];
  }
  
  /* check languages */
  
  e = [_langs objectEnumerator];
  while ((language = [e nextObject]) != nil) {
      NSString *rpath;
      
      if (debugOn) 
	[self logWithFormat:@"  check language (%@): '%@'", _name, language];
      rpath = [self _wePathForResourceNamed:_name inFramework:_fwName
		    language:language];
      if (rpath != nil) {
	if (debugOn) [self debugWithFormat:@"  FOUND: %@", rpath];
	return rpath;
      }
  }
  
  /* check without language */
  
  rpath = [self _wePathForResourceNamed:_name inFramework:_fwName
		language:nil];
  if (rpath != nil)
    return rpath;
  
  if (debugOn) {
    [self debugWithFormat:
	      @"did not find resource, try super lookup: '%@'", _name];
  }
    
  /* look using WOResourceManager */
  
  rpath = [super pathForResourceNamed:_name inFramework:_fwName
		 languages:_langs];
  return rpath;
}

/* locate WebServerResources */

- (NSString *)_urlForResourceNamed:(NSString *)_name
  inFramework:(NSString *)_fwName
  language:(NSString *)_lang
  applicationName:(NSString *)_appName
{
  NSString     *url;
  NSEnumerator *e;
  NSString     *path;
  
  if (debugOn) {
    [self logWithFormat:@"lookup URL of resource: '%@'/%@/%@", 
	    _name, _fwName, _lang];
  }
  
  /* check cache */
  
  url = checkCache(self->keyToURL, self->cachedKey, _name, _fwName, _lang);
  if (url != nil) {
    if (debugOn) {
      [self debugWithFormat:@"  found in cache: %@ (#%d)", url, 
	      [self->keyToURL count]];
    }
    return [url isNotNull] ? url : nil;
  }
  
  if (debugOn) {
    [self debugWithFormat:@"  not found in cache: %@ (%@,#%d)", 
	    url, self->cachedKey, [self->keyToURL count]];
  }
  
  /* check for framework resources */
  
  if ([_fwName length] > 0) {
    if (debugOn) 
      [self debugWithFormat:@"check framework: '%@'", _fwName];
    e = [wsPathes objectEnumerator];
    while ((path = [e nextObject])) {
      NSMutableString *ms;
      
      path = [path stringByAppendingPathComponent:_fwName];

      /* check language */
      if (_lang) {
        path = [path stringByAppendingString:_lang];
        path = [path stringByAppendingPathExtension:@"lproj"];
      }
      
      path = [path stringByAppendingPathComponent:_name];
      if (debugOn) [self debugWithFormat:@"  check path: '%@'", path];
      
      if (![fm fileExistsAtPath:path])
	continue;
        
      ms = [[NSMutableString alloc] initWithCapacity:256];
        
      if (prefix) [ms appendString:prefix];
      if (![ms hasSuffix:@"/"]) [ms appendString:@"/"];
      [ms appendString:_appName];
      if (suffix) [ms appendString:suffix];
      [ms appendString:[ms hasSuffix:@"/"] 
            ? @"WebServerResources/" : @"/WebServerResources/"];
      [ms appendString:_fwName];
      [ms appendString:@"/"];
      if (_lang) {
          [ms appendString:_lang];
          [ms appendString:@".lproj/"];
      }
      [ms appendString:_name];
      
      url = [ms copy];
      [ms release]; ms = nil;
      if (debugOn) [self debugWithFormat:@"FOUND: '%@'", url];
      goto done;
    }
  }
  
  /* check for global resources */
  
  if (debugOn) [self debugWithFormat:@"check global WebServerResources ..."];
  e = [wsPathes objectEnumerator];
  while ((path = [e nextObject])) {
    NSMutableString *ms;
    NSString *fpath, *basepath;
    
    /* check language */
    if (_lang) {
      basepath = [path stringByAppendingString:_lang];
      basepath = [basepath stringByAppendingPathExtension:@"lproj"];
    }
    else
      basepath = path;
    
    fpath = [basepath stringByAppendingPathComponent:_name];
    if (debugOn) {
      [self debugWithFormat:
	      @"  check path: '%@'\n base: %@\n name: %@\n "
	      @" path: %@\n lang: %@", 
	      fpath, basepath, _name, path, _lang];
    }
    
    if (![fm fileExistsAtPath:fpath])
      continue;
      
    ms = [[NSMutableString alloc] initWithCapacity:256];
      
    if (prefix) [ms appendString:prefix];
    if (![ms hasSuffix:@"/"]) [ms appendString:@"/"];
    [ms appendString:_appName];
    if (suffix) [ms appendString:suffix];
    [ms appendString:[ms hasSuffix:@"/"] 
          ? @"WebServerResources/" : @"/WebServerResources/"];
    if (_lang) {
      [ms appendString:_lang];
      [ms appendString:@".lproj/"];
    }
    [ms appendString:_name];
      
    url = [ms copy];
    [ms release]; ms = nil;
    if (debugOn) [self debugWithFormat:@"FOUND: '%@'", url];
    goto done;
  }
  
  /* finished processing */
  if (debugOn) {
    [self debugWithFormat:@"NOT FOUND: %@ (%@,#%d)", _name, self->cachedKey,
	    [self->keyToURL count]];
  }
  
 done:
  [self cacheValue:url inCache:self->keyToURL];
  return url;
}

- (NSString *)urlForResourceNamed:(NSString *)_name
  inFramework:(NSString *)_fwName
  languages:(NSArray *)_langs
  request:(WORequest *)_request
{
  NSEnumerator *e;
  NSString     *language;
  NSString     *url;
  NSString     *appName;
  
  if ([_name length] == 0) {
    if (debugOn) [self logWithFormat:@"got no name for resource URL lookup?!"];
    return nil;
  }
  
  if (debugOn) [self debugWithFormat:@"urlForResourceNamed: %@", _name];
  
  if (_langs == nil) {
    _langs = [_request browserLanguages];
    if (debugOn) {
      [self debugWithFormat:@"using browser languages: %@", 
	      [_langs componentsJoinedByString:@", "]];
    }
  }
  else if (debugOn) {
    [self debugWithFormat:@"using given languages: %@", 
	    [_langs componentsJoinedByString:@","]];
  }
  
  appName = [_request applicationName];
  if (appName == nil)
    appName = [(WOApplication *)[WOApplication application] name];
  
  /* check languages */
  
  e = [_langs objectEnumerator];
  while ((language = [e nextObject])) {
    NSString *url;
    
    if (debugOn) [self logWithFormat:@"  check language: '%@'", language];
    url = [self _urlForResourceNamed:_name
                inFramework:_fwName
                language:language
                applicationName:appName];
    if (url != nil) {
      if (debugOn) [self logWithFormat:@"  FOUND: %@", url];
      return url;
    }
  }
  
  /* check without language */
  
  url = [self _urlForResourceNamed:_name
              inFramework:_fwName
              language:nil
              applicationName:appName];
  if (url != nil)
    return url;

  if (debugOn) {
    [self debugWithFormat:
	    @"did not find resource in try super lookup: '%@'", _name];
  }
  
  url = [super urlForResourceNamed:_name
               inFramework:_fwName
               languages:_langs
               request:_request];

  return url;
}

/* locate components */

- (NSString *)lookupComponentInStandardPathes:(NSString *)_name
  inFramework:(NSString *)_framework theme:(NSString *)_theme
{
  // TODO: what about languages/themes?!
  NSEnumerator *e;
  NSString *path;
  
  if (debugComponents) {
    [self logWithFormat:@"lookup component in std pathes: %@|%@|%@",
	  _name, _framework, _theme];
  }
  
  if ([_theme isNotNull] && [_theme length] == 0) 
    _theme = nil;
  if ([_framework isNotNull] && [_framework length] == 0) 
    _framework = nil;
  
  e = [templatePathes objectEnumerator];
  while ((path = [e nextObject]) != nil) {
    NSString *pe;
    
    if (_theme != nil) {
      // TODO: should be lower case for FHS? or use a different path?
      path = [path stringByAppendingPathComponent:themesDirName];
      path = [path stringByAppendingPathComponent:_theme];
    }
    
    if (_framework != nil) {
      NSString *pureName;
      
      pureName = [_framework lastPathComponent];
      pureName = [pureName stringByDeletingPathExtension];
      path = [path stringByAppendingPathComponent:pureName];
    }
    
    path = [path stringByAppendingPathComponent:_name];
    
    pe = [path stringByAppendingPathExtension:@"wox"];
    if (debugComponents) [self logWithFormat:@"CHECK %@", pe];
    
    if ([fm fileExistsAtPath:pe])
      return pe;

    pe = [path stringByAppendingPathExtension:@"html"];
    if ([fm fileExistsAtPath:pe]) {
      /* 
	 Note: we are passing in the path of the HTML template, this is some
	       kind of hack to make the wrapper template builder look for the
               $name.html/$name.wod in there.
      */
      return pe;
    }
  }
  
  return nil;
}
- (NSString *)lookupComponentInStandardPathes:(NSString *)_name
  inFramework:(NSString *)_framework languages:(NSArray *)_langs
{
  NSString *path;
  NSString *theme;
  
  if (debugComponents) {
    [self logWithFormat:@"lookup component in std pathes(langs): %@|%@",
	  _name, _framework];
  }
  
  /* extract theme from language array (we do not support nested themes ATM) */
  
  theme = nil;
  if ([_langs count] > 1) {
    NSRange r;
    
    theme = [_langs objectAtIndex:0];
    r = [theme rangeOfString:@"_"];
    theme = (r.length > 0)
      ? [theme substringFromIndex:(r.location + r.length)]
      : nil;
  }
  else
    theme = nil;

  /* check theme dirs */
  
  if (theme != nil) {
    path = [self lookupComponentInStandardPathes:_name inFramework:_framework
		 theme:theme];
    if (path != nil)
      return path;
  }

  /* check base dirs */
  
  path = [self lookupComponentInStandardPathes:_name inFramework:_framework
	       theme:nil];
  if (path != nil)
    return path;
  
  /* check without framework subdir */

  if ([_framework length] > 0) {
    path = [self lookupComponentInStandardPathes:_name inFramework:nil
		 theme:nil];
    if (path != nil)
      return path;
  }

  return nil;
}

- (NSString *)lookupComponentPathUsingBundleManager:(NSString *)_name {
  // TODO: is this ever invoked?
  NSFileManager *fm = nil;
  NSString *wrapper = nil;
  NSBundle *bundle  = nil;
  NSString *path;

  if (debugComponents)
    [self logWithFormat:@"lookup component using bundle manager: %@", _name];
  
  bundle = [[self bundleManager]
                  bundleProvidingResource:_name
                  ofType:@"WOComponents"];
  if (bundle == nil) {
    [self debugWithFormat:@"did not find a bundle providing component: %@", 
	    _name];
    return nil;
  }
  
  if (debugOn) {
    [self debugWithFormat:@"bundle %@ for component %@",
            [[bundle bundlePath] lastPathComponent], _name];
  }
  
  [bundle load];
  
  fm      = [NSFileManager defaultManager];
  wrapper = [_name stringByAppendingPathExtension:@"wo"];
  
  path = [[bundle bundlePath] stringByAppendingPathComponent:@"Resources"];
  path = [path stringByAppendingPathComponent:wrapper];
  if ([fm fileExistsAtPath:path])
    return path;
  
  path = [[bundle bundlePath] stringByAppendingPathComponent:wrapper];
  if ([fm fileExistsAtPath:path])
    return path;
  
  return nil;
}

- (NSString *)pathToComponentNamed:(NSString *)_name
  inFramework:(NSString *)_fw languages:(NSArray *)_langs
{
  // TODO: what about languages in lookup?
  NSString *path;

  if (debugComponents) {
    [self logWithFormat:@"%s: lookup component: %@|%@",
	  __PRETTY_FUNCTION__, _name, _fw];
  }
  
  /* first check cache */
  
  path = checkCache(self->keyToComponentPath, self->cachedKey, _name, _fw,
		    [_langs count] > 0 ? [_langs objectAtIndex:0]:@"English");
  if (path != nil) {
    if (debugComponents)
      [self logWithFormat:@"  use cached location: %@", path];
    return [path isNotNull] ? path : nil;
  }
  
  /* look in FHS locations */

  path = [self lookupComponentInStandardPathes:_name inFramework:_fw
	       languages:_langs];
  if (path != nil) {
    if (debugComponents)
      [self logWithFormat:@"  found in standard pathes: %@", path];
    goto done;
  }
  
  /* try to find component by standard NGObjWeb method */
  
  if (debugComponents)
    [self logWithFormat:@"lookup component using WOResourceManager ..."];
  path = [super pathToComponentNamed:_name inFramework:_fw languages:_langs];
  if (path != nil) {
    if (debugComponents)
      [self logWithFormat:@"  found using WOResourceManager: %@", path];
    goto done;
  }
  
  /* find component using NGBundleManager */
  
  if ((path = [self lookupComponentPathUsingBundleManager:_name]) != nil) {
    if (debugComponents)
      [self logWithFormat:@"  found using bundle manager: %@", path];
    goto done;
  }
  
  /* did not find component */
 done:
  [self cacheValue:path inCache:self->keyToComponentPath];
  return path;
}

/* string tables */

- (NSString *)labelForKey:(NSString *)_key component:(WOComponent *)_component{
  return [self->labelManager labelForKey:_key component:_component];
}

- (NSString *)stringForKey:(NSString *)_key
  inTableNamed:(NSString *)_tableName
  withDefaultValue:(NSString *)_default
  languages:(NSArray *)_langs
{
  NSString *s;
  
  s = [self->labelManager 
	   stringForKey:_key inTableNamed:_tableName
	   withDefaultValue:_default languages:_langs];
  if (s != nil) return s;
  
  s = [super stringForKey:_key inTableNamed:_tableName
	     withDefaultValue:_default
	     languages:_langs];
  if (s != nil) return s;
  
  return s != nil ? s : _default;
}

/* debugging */

- (BOOL)isDebuggingEnabled {
  return debugOn;
}
- (NSString *)loggingPrefix {
  return @"[we-rm]";
}

@end /* WEResourceManager */

@implementation WOApplication(WEResourceManager)

- (NSString *)shareDirectoryName {
  return [[self name] lowercaseString];
}
- (NSString *)gsTemplatesDirectoryName {
  return [[self name] stringByAppendingString:@"/Templates/"];
}
- (NSString *)gsWebDirectoryName {
  return [[self name] stringByAppendingString:@"/WebServerResources/"];
}

@end /* WOApplication(WEResourceManager) */
