/*
  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 "SoProductResourceManager.h"
#include "SoProduct.h"
#include "SoObject.h"
#include "SoClassSecurityInfo.h"
#include <NGObjWeb/WOApplication.h>
#include <NGObjWeb/WOContext.h>
#include <NGObjWeb/WOResponse.h>
#include <NGObjWeb/WOSession.h>
#include <NGObjWeb/WORequest.h>
#include <NGExtensions/NSString+Ext.h>
#include <NGExtensions/NSBundle+misc.h>
#include "common.h"

@interface WOResourceManager(UsedPrivates)
- (NSString *)webServerResourcesPath;
- (NSString *)resourcesPath;
@end

@implementation SoProductResourceManager

static NGBundleManager *bm = nil;
static BOOL debugOn = NO;

+ (void)initialize {
  NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
  
  bm = [[NGBundleManager defaultBundleManager] retain];
  debugOn = [ud boolForKey:@"SoProductResourceManagerDebugEnabled"];
}

- (id)initWithProduct:(SoProduct *)_product {
  if ((self = [super initWithPath:[[_product bundle] bundlePath]])) {
    self->product = _product;
  }
  return self;
}

/* containment */

- (void)detachFromContainer {
  self->product = nil;
}
- (id)container {
  return self->product;
}
- (NSString *)nameInContainer {
  return @"Resources";
}

/* lookup resources */

- (NSString *)pathForResourceNamed:(NSString *)_name
  inFramework:(NSString *)_frameworkName
  languages:(NSArray *)_languages
{
  // TODO: should we do acquisition? (hm, don't think so!, done in lookup)
  //       but maybe we should not fall back to WOApplication resources
  NSBundle *bundle;
  NSString *path;
  
  if (debugOn) [self debugWithFormat:@"lookup resource: '%@'", _name];
  
  /* determine product bundle or explicitly requested framework/bundle */
  
  if ([_frameworkName length] > 0) {
    if ([_frameworkName hasPrefix:@"/"]) {
      bundle = [bm bundleWithPath:_frameworkName];
    }
    else {
      bundle = [bm bundleWithName:
                     [_frameworkName stringByDeletingPathExtension]
                   type:[_frameworkName pathExtension]];
    }
    if (bundle == nil) {
      [self debugWithFormat:@"WARNING: missing bundle for framework: '%@'",
              _frameworkName];
      goto fallback;
    }
  }
  else {
    if ((bundle = [self->product bundle]) == nil) {
      [self debugWithFormat:@"WARNING: missing bundle for product: %@",
              self->product];
      goto fallback;
    }
  }
  
  if (debugOn) [self debugWithFormat:@"  bundle: %@", bundle];
  
  /* lookup resource in bundle */
  
  path = [bundle pathForResource:[_name stringByDeletingPathExtension]
                 ofType:[_name pathExtension]
                 inDirectory:nil
                 forLocalizations:_languages];
  if (path != nil) {
    if (debugOn) [self debugWithFormat:@"  => found: %@", path];
    return path;
  }
  if (debugOn) [self debugWithFormat:@"  resource not found in bundle ..."];
  
  /* fall back to WOResourceManager lookup */
 fallback:
  return [super pathForResourceNamed:_name inFramework:_frameworkName
                languages:_languages];
}

/* generate URL for resources (eg filename binding in WOImage) */

- (NSString *)webServerResourcesPath {
  /* to avoid warning that WebServerResources path does not exist ... */
  return [[[WOApplication application] resourceManager]
                          webServerResourcesPath];
}

- (NSString *)urlForResourceNamed:(NSString *)_name
  inFramework:(NSString *)_frameworkName
  languages:(NSArray *)_languages
  request:(WORequest *)_request
{
  NSString *resource = nil, *tmp;
  NSString *path = nil, *sbase;
  unsigned len;
  NSString *url;
  
  if (debugOn) [self debugWithFormat:@"lookup url: '%@'", _name];
  
  if (_languages == nil) _languages = [_request browserLanguages];
  
  resource = [self pathForResourceNamed:_name
                   inFramework:_frameworkName
                   languages:_languages];
#if APPLE_Foundation_LIBRARY || NeXT_Foundation_LIBRARY
  if ([resource rangeOfString:@"/Contents/"].length > 0) {
    resource = [resource stringByReplacingString:@"/Contents"
                         withString:@""];
  }
#endif
#if 0
  tmp = [resource stringByStandardizingPath];
  if (tmp) resource = tmp;
#endif
  
  if (resource == nil) {
    if (debugOn) {
      [self debugWithFormat:@"  => not found '%@' (fw=%@,langs=%@)", 
            _name, _frameworkName, [_languages componentsJoinedByString:@","]];
    }
    return nil;
  }
  
  sbase = self->base;
  tmp  = [sbase commonPrefixWithString:resource options:0];
  
  len  = [tmp length];
  path = [sbase    substringFromIndex:len];
  tmp  = [resource substringFromIndex:len];
  if (([path length] > 0) && ![tmp hasPrefix:@"/"] && ![tmp hasPrefix:@"\\"])
    path = [path stringByAppendingString:@"/"];
  path = [path stringByAppendingString:tmp];
  
#ifdef __WIN32__
  {
    NSArray *cs;
    cs   = [path componentsSeparatedByString:@"\\"];
    path = [cs componentsJoinedByString:@"/"];
  }
#endif
  if (path == nil)
    return nil;
  
  if ([path hasPrefix:@"/Resources/"])
    path = [path substringFromIndex:11];
  else if ([path hasPrefix:@"Resources/"])
    path = [path substringFromIndex:10];
  
  /* Note: cannot use -stringByAppendingPathComponent: on OSX! */
  url = [self baseURLInContext:[[WOApplication application] context]];
  if (debugOn) [self debugWithFormat:@" base: '%@'", url];
  
  if (![url hasSuffix:@"/"]) url = [url stringByAppendingString:@"/"];
  url = [url stringByAppendingString:path];
  
  if (debugOn) [self debugWithFormat:@"  => '%@'", url];
  return url;
}

- (WOElement *)templateWithName:(NSString *)_name
  languages:(NSArray *)_languages
{
  [self logWithFormat:@"lookup template with name '%@' (languages=%@)",
	  _name, [_languages componentsJoinedByString:@","]];
  return [super templateWithName:_name languages:_languages];
}

/* resource manager as a SoObject */

- (NSString *)mimeTypeForExtension:(NSString *)_ext {
  // TODO: HACK, move to some object
  NSString *ctype = nil;
  
  if ([_ext isEqualToString:@"css"])       ctype = @"text/css";
  else if ([_ext isEqualToString:@"gif"])  ctype = @"image/gif";
  else if ([_ext isEqualToString:@"jpg"])  ctype = @"image/jpeg";
  else if ([_ext isEqualToString:@"png"])  ctype = @"image/png";
  else if ([_ext isEqualToString:@"html"]) ctype = @"text/html";
  else if ([_ext isEqualToString:@"xml"])  ctype = @"text/xml";
  else if ([_ext isEqualToString:@"txt"])  ctype = @"text/plain";
  else if ([_ext isEqualToString:@"js"])   ctype = @"application/x-javascript";
  else if ([_ext isEqualToString:@"xhtml"]) ctype = @"application/xhtml+xml";
  return ctype;
}

- (id)lookupName:(NSString *)_key inContext:(id)_ctx acquire:(BOOL)_flag {
  WOResponse *r;
  NSBundle   *b;
  NSString   *p, *pe, *ctype;
  NSData     *data;
  NSArray    *languages = nil;
  
  /* TODO: add support for languages (eg English.lproj/ok.gif) ! */
  
  /* check whether the resource is made public */
  
  if (![self->product isPublicResource:_key]) {
    [self debugWithFormat:@"key '%@' is not declared a public resource.",_key];
    return nil;
  }
  
  if ((b = [self->product bundle]) == nil) {
    [self debugWithFormat:@"product has no bundle for lookup of %@", _key];
    return nil;
  }
  
  pe = [_key pathExtension];

  /* ask resource-manager (self) for path */
  
  languages = [_ctx hasSession]
    ? [(WOSession *)[_ctx session] languages]
    : [[(id <WOPageGenerationContext>)_ctx request] browserLanguages];
  
  p = [self pathForResourceNamed:_key 
	    inFramework:[b bundlePath]
	    languages:languages];
  if (p == nil) {
    [self logWithFormat:@"ERROR: did not find product resource: %@", _key];
    return nil;
  }

  /* load data */

  if ((data = [NSData dataWithContentsOfMappedFile:p]) == nil) {
    [self logWithFormat:@"ERROR: failed to load product resource: %@", _key];
    return nil;
  }
  
  /* and deliver as a complete response */
  
  r = [(id<WOPageGenerationContext>)_ctx response];
  
  [r setStatus:200 /* OK */];
  [r setContent:data];
  
  if ((ctype = [self mimeTypeForExtension:pe]) == nil) {
    [self logWithFormat:
	    @"WARNING: did not recognize extension '%@', "
	    @"delivering as application/octet-stream.", pe];
    ctype = @"application/octet-stream";
  }

  {
    NSDate *expDate = nil;
    NSString *str = nil;
    
    expDate = [[NSDate alloc] initWithTimeInterval:(60 * 60 * 1) /* 1 hour */
			      sinceDate:[NSDate date]];
    str = [expDate descriptionWithCalendarFormat:
		     @"%a, %d %b %Y %H:%M:%S GMT"
		   timeZone:[NSTimeZone timeZoneWithAbbreviation:@"GMT"]
		   locale:nil];
    [r setHeader:str forKey:@"expires"];
    [expDate release];
  }
  
  [r setHeader:ctype forKey:@"content-type"];
  return r;
}

/* debugging */

- (BOOL)isDebuggingEnabled {
  return debugOn;
}
- (NSString *)loggingPrefix {
  return [NSString stringWithFormat:@"[RM:%@]", [self->product productName]];
}

/* description */

- (NSString *)description {
  NSMutableString *str;

  str = [NSMutableString stringWithCapacity:64];
  [str appendFormat:@"<%@[0x%08X]:", NSStringFromClass([self class]), self];
  [str appendFormat:@" product='%@'", [self->product productName]];
  [str appendString:@">"];
  return str;
}

@end /* SoProductResourceManager */
