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

@implementation WEStringTableManager

static NSNull *null = nil;
static BOOL   debugOn = NO;

+ (void)initialize {
  NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
  
  if (null == nil) null = [[NSNull null] retain];
  
  if ((debugOn = [ud boolForKey:@"WEStringTableManagerDebugEnabled"]))
    NSLog(@"Note: WEStringTableManager debugging is enabled.");
}

- (id)init {
  if ((self = [super init])) {
    if ([WOApplication isCachingEnabled]) {
      // TODO: find proper capacities
      self->nameToTable = 
	[[NSMutableDictionary alloc] initWithCapacity:16];
      self->compLabelCache = 
	[[NSMutableDictionary alloc] initWithCapacity:512];
    }
    else
      [self logWithFormat:@"Note: label caching is disabled (slow!)."];
  }
  return self;
}

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

/* accessors */

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

/* resource pathes */

+ (NSString *)findResourceDirectoryNamed:(NSString *)_name
  fhsName:(NSString *)_fhs
{
  static NSDictionary *env = nil;
  NSFileManager *fm;
  NSString      *path;
  id tmp;
  
  if (env == nil) env = [[[NSProcessInfo processInfo] environment] retain];
  fm = [NSFileManager defaultManager];

  /* look in GNUstep pathes */
  
  tmp = [[WEResourceManager rootPathesInGNUstep] objectEnumerator];
  while ((path = [tmp nextObject]) != nil) {
    path = [path stringByAppendingPathComponent:_name];
    
    if ([fm fileExistsAtPath:path])
      return path;
  }
  
  /* look in FHS pathes */
  
  tmp = [[WEResourceManager rootPathesInFHS] objectEnumerator];
  while ((path = [tmp nextObject])) {
    path = [path stringByAppendingPathComponent:_fhs];
    if ([fm fileExistsAtPath:path])
      return path;
  }
  return nil;
}

/* labels */

- (NSString *)labelForKey:(NSString *)_key component:(WOComponent *)_component{
  NSArray         *langs;
  NSBundle        *bundle;
  id              value = nil;
  NGBundleManager *bm;
  NSString        *cname;
  id cacheKey;
  
  if (![_key isNotEmpty])
    return _key;
  if (_component == nil)
    return _key;

  if (null == nil) null = [[NSNull null] retain];
  
  cname = [_component name];
  langs = [(WOSession *)[_component session] languages];
  
  /* cache ?? */
  
  {
    /* cache cachekeys here ;-) */
    cacheKey = [NSArray arrayWithObjects:_key, cname, langs, nil];
  }
  
  if ((value = [self->compLabelCache objectForKey:cacheKey]) != nil) {
    if (value == null) value = nil;
    return value;
  }
  
  /* lookup bundle */
  
  bm = [NGBundleManager defaultBundleManager];
  bundle = [bm bundleProvidingResource:cname ofType:@"WOComponents"];
  if (bundle == nil)
    bundle = [NGBundle bundleForClass:[_component class]];
  
  /* look in BundleName.strings */
  
  value = [self stringForKey:_key
                inTableNamed:[bundle bundleName]
                withDefaultValue:nil
                languages:langs];
  if (value)
    goto done;
  
  /* look in App.strings */
  value = [self stringForKey:_key
                inTableNamed:cname
                withDefaultValue:nil
                languages:langs];
  if (value)
    goto done;
  
  /* no string found, use key as string */
  value = _key;
  
 done:
#if DEBUG
  NSAssert(value, @"missing value ..");
#endif
  
  [self->compLabelCache setObject:value forKey:cacheKey];
#if 0
  NSLog(@"%s: label cache size now: %i", __PRETTY_FUNCTION__,
        [self->compLabelCache count]);
#endif
  return value;
}

- (NSString *)_cachedStringForKey:(NSString *)_key
  inTableNamed:(NSString *)_tableName
  withDefaultValue:(NSString *)_default
  languages:(NSArray *)_languages
{
  if (_tableName == nil) _tableName = @"default";

  /* look into string cache */
  
  if ([_languages count] > 0) {
    NSString *tname;
    NSRange  r;
    NSString *language;
    id       table;
    
    language = [_languages objectAtIndex:0];
    
    r = [language rangeOfString:@"_"];
    if (r.length > 0)
      language = [language substringToIndex:r.location];
    
    tname = [language stringByAppendingString:@"+++"];
    tname = [tname stringByAppendingString:_tableName];
    
    if ((table = [self->nameToTable objectForKey:tname])) {
      if (debugOn) {
	[self logWithFormat:
		@"resolved label %@ table %@ in lang %@ (languages=%@)",
                _key, _tableName, language,
                [_languages componentsJoinedByString:@","]];
      }
      return [table stringForKey:_key withDefaultValue:_default];
    }
  }
  else if ([_languages count] == 0) {
    id table;
    
    NSLog(@"WARNING: called %s without languages array %@ !",
          __PRETTY_FUNCTION__, _languages);
    
    if ((table = [self->nameToTable objectForKey:_tableName]))
      return [table stringForKey:_key withDefaultValue:_default];
  }
  
  return nil;
}

- (WEStringTable *)bundleTableWithName:(NSString *)_tableName
  languages:(NSArray *)_languages
{
  NSBundle     *bundle;
  NSString     *path;
  NSEnumerator *e;
  NSString     *language;
  
  if (_tableName == nil) _tableName = @"default";
  
  bundle = [[self bundleManager]
                  bundleProvidingResource:_tableName
                  ofType:@"strings"];
  if (bundle == nil)
    return nil;

  e = [_languages objectEnumerator];
  while ((language = [e nextObject])) {
    NSArray *ls;
    NSRange r;
      
    r = [language rangeOfString:@"_"];
    if (r.length > 0)
      language = [language substringToIndex:r.location];
      
    ls = [NSArray arrayWithObject:language];
      
    path = [bundle pathForResource:_tableName
                   ofType:@"strings"
                   languages:ls];
    if (path != nil) {
      NSString *tname;
      id table;
      
      tname = [language stringByAppendingString:@"+++"];
      tname = [tname stringByAppendingString:_tableName];
        
      table = [WEStringTable stringTableWithPath:path];
      [self->nameToTable setObject:table forKey:tname];
      return table;
    }
  }
    
  path = [bundle pathForResource:_tableName
                 ofType:@"strings"
                 languages:nil];
    
  if (path != nil) {
    WEStringTable *table;
    
    table = [WEStringTable stringTableWithPath:path];
    
    [self->nameToTable setObject:table forKey:_tableName];
    return table;
  }
  
  if (debugOn) {
    // TODO: use logging
    NSLog(@"WARNING: missing string table %@ in bundle %@",
          _tableName, [bundle bundlePath]);
  }
  return nil;
}

+ (NSString *)gsTranslationsSubpath {
  NSString *p;
  
  p = [[WOApplication application] gsTranslationsDirectoryName];
  if (![p isNotEmpty])
    return nil;
  
  p = [@"Resources/" stringByAppendingString:p];
  return p;
}
+ (NSString *)fhsTranslationsSubpath {
  NSString *p;
  
  p = [[WOApplication application] shareTranslationsDirectoryName];
  if (![p isNotEmpty])
    return nil;
  
  p = [@"share/" stringByAppendingString:p];
  return [p stringByAppendingString:@"/translations/"];
}

+ (NSArray *)availableTranslations {
  static NSArray *ltranslations = nil;
  NSMutableSet  *translations;
  NSEnumerator  *e;
  NSFileManager *fm;
  NSString      *path;
  NSArray       *translationPathes;

  if (ltranslations != nil)
    return ltranslations;
  
  // TODO: use lists ..., or maybe NGResourceLocator
  path = [self findResourceDirectoryNamed:[self gsTranslationsSubpath]
               fhsName:[self fhsTranslationsSubpath]];
  if (path == nil)
    return nil;

  translationPathes = [NSArray arrayWithObject:path];
  translations = [NSMutableSet setWithCapacity:16];
  fm           = [NSFileManager defaultManager];
  
  e = [translationPathes objectEnumerator];
  while ((path = [e nextObject]) != nil) {
    NSEnumerator *dl;
    NSString *l;
    
    dl = [[fm directoryContentsAtPath:path] objectEnumerator];
    while ((l = [dl nextObject]) != nil) {
      if (![l hasSuffix:@".lproj"])
	continue;
      if ([l hasPrefix:@"."])
	continue;
      
      l = [l stringByDeletingPathExtension];
      [translations addObject:l];
    }
  }
  
  ltranslations = [[translations allObjects] copy];
  if ([ltranslations count] > 0) {
    NSLog(@"Note: located translations: %@", 
	  [ltranslations componentsJoinedByString:@", "]);
  }
  else
    NSLog(@"Note: located no additional translations.");
  return ltranslations;
}

- (WEStringTable *)resourceTableWithName:(NSString *)_tableName
  languages:(NSArray *)_languages
{
  NSString      *rpath, *path;
  NSEnumerator  *e;
  NSString      *language;
  NSFileManager *fm;

  if (_tableName == nil) _tableName = @"default";
  
  fm = [NSFileManager defaultManager];
  // TODO: should be NGResourceLocator method?
  rpath = [[self class] findResourceDirectoryNamed:
                          [[self class] gsTranslationsSubpath]
			fhsName:
                          [[self class] fhsTranslationsSubpath]];
  if (rpath == nil) {
    [self logWithFormat:@"missing translations directory ..."];
    return nil;
  }
  
  /* look into language projects .. */
  
  e = [_languages objectEnumerator];
  while ((language = [e nextObject]) != nil) {
    WEStringTable *table;
    NSRange  r;
    NSString *tname;
      
    r = [language rangeOfString:@"_"];
    if (r.length > 0)
      language = [language substringToIndex:r.location];
    
    // TODO: add support for .po
    path = [_tableName stringByAppendingPathExtension:@"strings"];
    path = [[language stringByAppendingPathExtension:@"lproj"]
                      stringByAppendingPathComponent:path];
    path = [rpath stringByAppendingPathComponent:path];
    
    if (![fm fileExistsAtPath:path])
      continue;
    
    tname = [language stringByAppendingString:@"+++"];
    tname = [tname stringByAppendingString:_tableName];
    
    table = [WEStringTable stringTableWithPath:path];
    [self->nameToTable setObject:table forKey:tname];
    
    return table;
  }
  
  /* look into resource dir */
  
  // TODO: add support for .po
  path = [_tableName stringByAppendingPathExtension:@"strings"];
  path = [rpath stringByAppendingPathComponent:path];

  if ([fm fileExistsAtPath:path]) {
    WEStringTable *table;
    
    table = [WEStringTable stringTableWithPath:path];
    if (self->nameToTable == nil)
      self->nameToTable = [[NSMutableDictionary alloc] initWithCapacity:16];
    
    [self->nameToTable setObject:table forKey:_tableName];
    return table;
  }
  
  // TODO: use SOPE 4.5 logging framework
  if (debugOn)
    NSLog(@"WARNING: missing string table %@ in %@", _tableName, path);
  return nil;
}

- (NSString *)stringForKey:(NSString *)_key
  inTableNamed:(NSString *)_tableName
  withDefaultValue:(NSString *)_default
  languages:(NSArray *)_languages
{
  WEStringTable *table;
  NSString *s;
  
  if (_tableName == nil) // TODO: Localizable in NGObjWeb
    _tableName = @"default";
  
  /* look into string cache */
  
  s = [self _cachedStringForKey:_key
            inTableNamed:_tableName
            withDefaultValue:_default
            languages:_languages];
  if (s != nil) return s;
  
  if (self->nameToTable == nil)
    self->nameToTable = [[NSMutableDictionary alloc] initWithCapacity:16];
  
  /* search for string */
  
  if ((table = [self resourceTableWithName:_tableName languages:_languages])){
    if ((s = [table stringForKey:_key withDefaultValue:nil]) != nil)
      return s;
  }

  if ((table = [self bundleTableWithName:_tableName languages:_languages])){
    if ((s = [table stringForKey:_key withDefaultValue:nil]) != nil)
      return s;
  }
  
  // TODO: shouldn't we return the default value?
  return nil;
}

- (id)stringTableWithName:(NSString *)_tableName
  inFramework:(NSString *)_framework
  languages:(NSArray *)_languages
{
  WEStringTable *table;
  
  if ((table = [self resourceTableWithName:_tableName languages:_languages]))
    return table;
  if ((table = [self bundleTableWithName:_tableName languages:_languages]))
    return table;
  
  return nil;
}

/* debugging */

- (BOOL)isDebuggingEnabled {
  return debugOn;
}

@end /* WEStringTableManager */

@implementation WOApplication(WEStringTableManager)

- (NSString *)shareTranslationsDirectoryName {
  return [[self name] lowercaseString];
}
- (NSString *)gsTranslationsDirectoryName {
  return [self name];
}

@end /* WOApplication(WEStringTableManager) */
