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

#if GNUSTEP_BASE_LIBRARY
@implementation NSBundle(Copying)
- (id)copyWithZone:(NSZone *)_zone {
  return [self retain];
}
@end
#endif

@implementation SaxXMLReaderFactory

static BOOL    coreOnMissingParser = NO;
static BOOL    debugOn       = NO;
static NSArray *searchPathes = nil;
static NSNull  *null         = nil;
static id      factory       = nil;

+ (void)initialize {
  NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
  
  coreOnMissingParser = [ud boolForKey:@"SaxCoreOnMissingParser"];
  debugOn             = [ud boolForKey:@"SaxDebugReaderFactory"];
}

+ (id)standardXMLReaderFactory {
  if (factory == nil)
    factory = [[self alloc] init];
  return factory;
}

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

/* operations */

- (void)flush {
  [self->nameToBundle   release]; self->nameToBundle   = nil;
  [self->mimeTypeToName release]; self->mimeTypeToName = nil;
}

- (NSArray *)saxReaderSearchPathes {
  NSMutableArray *ma;
  id tmp;
#if !COCOA_Foundation_LIBRARY
  NSDictionary   *env;
#endif

  if (searchPathes)
    return searchPathes;
    
  ma  = [NSMutableArray arrayWithCapacity:8];

#if COCOA_Foundation_LIBRARY
  tmp = NSSearchPathForDirectoriesInDomains(NSAllLibrariesDirectory,
                                              NSAllDomainsMask,
                                              YES);
  if ([tmp count] > 0) {
      NSEnumerator *e;
      
      e = [tmp objectEnumerator];
      while ((tmp = [e nextObject])) {
        tmp = [tmp stringByAppendingPathComponent:@"SaxDrivers-4.4"];
        if (![ma containsObject:tmp])
          [ma addObject:tmp];
      }
  }
#if COMPILE_AS_FRAMEWORK
  {
      NSBundle *fwBundle;
      
      /* no need to add 4.4 here, right? */
      fwBundle = [NSBundle bundleForClass:[self class]];
      [ma addObject:[[fwBundle resourcePath]
                               stringByAppendingPathComponent:@"SaxDrivers"]];
  }
#endif
#else
  env = [[NSProcessInfo processInfo] environment];
  
  if ((tmp = [env objectForKey:@"GNUSTEP_PATHPREFIX_LIST"]) == nil)
    tmp = [env objectForKey:@"GNUSTEP_PATHLIST"];
  tmp = [tmp componentsSeparatedByString:@":"];
  if ([tmp count] > 0) {
      NSEnumerator *e;
      
      e = [tmp objectEnumerator];
      while ((tmp = [e nextObject])) {
        tmp = [tmp stringByAppendingPathComponent:@"Library/SaxDrivers-4.4"];
        if (![ma containsObject:tmp])
          [ma addObject:tmp];
      }
  }
#endif
  
  /* FHS fallback */
  [ma addObject:@"/usr/local/lib/sope-4.4/saxdrivers/"];
  [ma addObject:@"/usr/lib/sope-4.4/saxdrivers/"];
  searchPathes = [ma copy];
  
  if ([searchPathes count] == 0)
    NSLog(@"%s: no search pathes were found!", __PRETTY_FUNCTION__);
  
  return searchPathes;
}

- (void)_loadBundlePath:(NSString *)_bundlePath
  infoDictionary:(NSDictionary *)_info
  nameMap:(NSMutableDictionary *)_nameMap
  typeMap:(NSMutableDictionary *)_typeMap
{
  NSArray      *drivers;
  NSEnumerator *e;
  NSDictionary *driverInfo;
  NSBundle     *bundle;

  _info = [_info objectForKey:@"provides"];
  if ((drivers = [_info objectForKey:@"SAXDrivers"]) == nil) {
    NSLog(@"%s: .sax bundle '%@' does not provide any SAX drivers ...",
          __PRETTY_FUNCTION__, _bundlePath);
    return;
  }
  
  if ((bundle = [NSBundle bundleWithPath:_bundlePath]) == nil) {
    NSLog(@"%s: could not create bundle from path '%@'",
          __PRETTY_FUNCTION__, _bundlePath);
    return;
  }
  
  /* found a driver with valid info dict, process it ... */
  
  e = [drivers objectEnumerator];
  while ((driverInfo = [e nextObject])) {
    NSString     *name, *tname;
    NSEnumerator *te;
    
    name = [driverInfo objectForKey:@"name"];
    if ([name length] == 0) {
      NSLog(@"%s: missing name in sax driver section of bundle %@ ...",
            __PRETTY_FUNCTION__, _bundlePath);
      continue;
    }

    /* check if name is already registered */
    if ([_nameMap objectForKey:name]) {
      if (debugOn)
        NSLog(@"%s: already have sax driver named '%@' ...",
              __PRETTY_FUNCTION__, name);
      continue;
    }

    /* register bundle for name */
    [_nameMap setObject:bundle forKey:name];

    /* register MIME-types */
    te = [[driverInfo objectForKey:@"sourceTypes"] objectEnumerator];
    while ((tname = [te nextObject])) {
      NSString *tmp;
      
      if ((tmp = [_typeMap objectForKey:tname])) {
        NSLog(@"WARNING(%s): multiple parsers available for MIME type '%@', "
              @"using '%@' as default for type %@.", 
              __PRETTY_FUNCTION__, tname, tmp, tname);
        continue;
      }
      
      [_typeMap setObject:name forKey:tname];
    }
  }
}

- (void)_loadLibraryPath:(NSString *)_libraryPath
  nameMap:(NSMutableDictionary *)_nameMap
  typeMap:(NSMutableDictionary *)_typeMap
{
  NSFileManager *fm = [NSFileManager defaultManager];
  NSEnumerator  *e;
  NSString      *p;
  
  e = [[fm directoryContentsAtPath:_libraryPath] objectEnumerator];
  while ((p = [e nextObject])) {
    NSDictionary *info;
    NSString     *infoPath;
    BOOL         isDir;
    
    if (![p hasSuffix:@".sax"]) continue;
    
    p = [_libraryPath stringByAppendingPathComponent:p];
    if (![fm fileExistsAtPath:p isDirectory:&isDir])
      continue;
    if (!isDir) { /* info file is a directory ??? */
      NSLog(@"%s: .sax is not a dir: '%@' ???", __PRETTY_FUNCTION__, p);
      continue;
    }
    
#if COCOA_Foundation_LIBRARY
    {
      NSBundle *b;
      
      b = [NSBundle bundleWithPath:p];
      infoPath = [b pathForResource:@"bundle-info" ofType:@"plist"];
    }
#else
    infoPath = [p stringByAppendingPathComponent:@"bundle-info.plist"];
#endif
    
    info = [NSDictionary dictionaryWithContentsOfFile:infoPath];
    if (info == nil) {
      NSLog(@"%s: could not parse bundle-info dictionary: '%@'",
            __PRETTY_FUNCTION__, infoPath);
      continue;
    }
    
    [self _loadBundlePath:p infoDictionary:info
          nameMap:_nameMap typeMap:_typeMap];
  }
}

- (void)_loadAvailableBundles {
  NSAutoreleasePool *pool;
  
  /* setup globals */
  if (null == nil)
    null = [[NSNull null] retain];

#if DEBUG
  NSAssert(self->nameToBundle   == nil, @"already set up !");
  NSAssert(self->mimeTypeToName == nil, @"partially set up !");
#else
  if (self->nameToBundle) return;
#endif
  
  pool = [[NSAutoreleasePool alloc] init];
  {
    /* lookup bundle in Libary/SaxDrivers pathes */
    NSEnumerator        *pathes;
    NSString            *path;
    NSMutableDictionary *nameMap, *typeMap;

    nameMap = [NSMutableDictionary dictionaryWithCapacity:16];
    typeMap = [NSMutableDictionary dictionaryWithCapacity:16];
    
    pathes = [[self saxReaderSearchPathes] objectEnumerator];
    while ((path = [pathes nextObject]))
      [self _loadLibraryPath:path nameMap:nameMap typeMap:typeMap];
    
    self->nameToBundle   = [nameMap copy];
    self->mimeTypeToName = [typeMap copy];

#if DEBUG
    if ([self->nameToBundle count] == 0) {
      NSLog(@"%s: no XML parser could be found ...", __PRETTY_FUNCTION__);
    }
    else if ([self->mimeTypeToName count] == 0) {
      NSLog(@"%s: no XML parser declared a MIME type ...",__PRETTY_FUNCTION__);
    }
#endif
  }
  [pool release];
}

- (id<NSObject,SaxXMLReader>)createXMLReader {
  NSString *defReader;
  id       reader;

  if (debugOn) NSLog(@"%s: lookup default XML reader ...",__PRETTY_FUNCTION__);
  
  defReader = 
    [[NSUserDefaults standardUserDefaults] stringForKey:@"XMLReader"];

  if (debugOn) NSLog(@"%s:   default name ...",__PRETTY_FUNCTION__, defReader);
  
  if (defReader) {
    if ((reader = [self createXMLReaderWithName:defReader]))
      return reader;

    NSLog(@"%s: could not create default XMLReader '%@' !",
          __PRETTY_FUNCTION__, defReader);
  }
  
  return [self createXMLReaderForMimeType:@"text/xml"];
}

- (id<NSObject,SaxXMLReader>)createXMLReaderWithName:(NSString *)_name {
  NSBundle *bundle;
  Class    readerClass;
  
  if (debugOn)
    NSLog(@"%s: lookup XML reader with name: '%@'",__PRETTY_FUNCTION__,_name);
  
  if ([_name length] == 0)
    return [self createXMLReader];
  
  if (self->nameToBundle == nil)
    [self _loadAvailableBundles];
  
  if ((bundle = [self->nameToBundle objectForKey:_name]) == nil)
    return nil;
  
  /* load bundle executable code */
  if (![bundle load]) {
    NSLog(@"%s: could not load SaxDriver bundle %@ !", __PRETTY_FUNCTION__,
          bundle);
    return nil;
  }
  
  NSAssert(bundle, @"should have a loaded bundle at this stage ...");
  
  /* lookup class */
  if ((readerClass = NSClassFromString(_name)) == Nil) {
    NSLog(@"WARNING(%s): could not find SAX reader class %@ (SAX bundle=%@)",
          __PRETTY_FUNCTION__, _name, bundle);
    return nil;
  }
  
  /* create instance */
  return [[[readerClass alloc] init] autorelease];
}

- (id<NSObject,SaxXMLReader>)createXMLReaderForMimeType:(NSString *)_mtype {
  id<NSObject,SaxXMLReader> reader;
  NSString *name;

  if (self->mimeTypeToName == nil)
    [self _loadAvailableBundles];
  
  if (debugOn)
    NSLog(@"%s: lookup XML reader for type: '%@'",__PRETTY_FUNCTION__, _mtype);
  
  if ([_mtype respondsToSelector:@selector(stringValue)])
    _mtype = [(id)_mtype stringValue];
  if ([_mtype length] == 0)
    _mtype = @"text/xml";
  
  if ((name = [self->mimeTypeToName objectForKey:_mtype]) == nil) {
    if (debugOn) {
      NSLog(@"%s: did not find SAX parser for MIME type %@ map: \n%@",
	    __PRETTY_FUNCTION__, _mtype, self->mimeTypeToName);
      NSLog(@"%s: parsers: %@", __PRETTY_FUNCTION__, 
	    [self->nameToBundle allKeys]);
    }
    if (coreOnMissingParser) {
      NSLog(@"%s: aborting, because 'SaxCoreOnMissingParser' "
	    @"default is enabled!", __PRETTY_FUNCTION__);
      abort();
    }
    return nil;
  }
  if (debugOn) 
    NSLog(@"%s:  found XML reader named: '%@'", __PRETTY_FUNCTION__, name);
  
  reader = [self createXMLReaderWithName:name];

  if (debugOn)
    NSLog(@"%s:  created XML reader: %@", __PRETTY_FUNCTION__, reader);
  return reader;
}

- (NSArray *)availableXMLReaders {
  return [self->nameToBundle allKeys];
}

@end /* SaxXMLReaderFactory */
