/*
  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 "SaxObjectModel.h"
#include "common.h"

#if NeXT_Foundation_LIBRARY || COCOA_Foundation_LIBRARY || \
    APPLE_FOUNDATION_LIBRARY
bool _CFArrayIsMutable(CFArrayRef dict);
#endif

static NSDictionary *mapDictsToObjects(NSDictionary *_dict, Class clazz) {
  NSMutableDictionary *md;
  NSEnumerator *e;
  NSString     *key;
  
  md = [NSMutableDictionary dictionaryWithCapacity:16];
  
  e = [_dict keyEnumerator];
  while ((key = [e nextObject])) {
    id obj;

    obj = [[clazz alloc] initWithDictionary:[_dict objectForKey:key]];
    [md setObject:obj forKey:key];
    [obj release];
  }
  return md;
}

@implementation SaxObjectModel

static BOOL    doDebug = NO;
static NSArray *searchPathes = nil;

#if NeXT_Foundation_LIBRARY || COCOA_Foundation_LIBRARY || \
    APPLE_FOUNDATION_LIBRARY
static Class NSCFArrayClass = Nil;

+ (void)initialize {
  static BOOL isInitialized = NO;
  
  if (isInitialized) return;
  isInitialized = YES;
    NSCFArrayClass = NSClassFromString(@"NSCFArray");
}
#endif

+ (NSArray *)saxMappingSearchPathes {
  if (searchPathes == nil) {
    NSMutableArray *ma;
    NSDictionary   *env;
    id tmp;
    
    env = [[NSProcessInfo processInfo] environment];
    ma  = [NSMutableArray arrayWithCapacity:6];

#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:@"SaxMappings"];
        if (![ma containsObject:tmp])
          [ma addObject:tmp];
      }
    }
#else
    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/SaxMappings"];
        if (![ma containsObject:tmp])
          [ma addObject:tmp];
      }
    }
#endif
    
    /* FHS fallback */
    [ma addObject:@"/usr/local/share/sope-4.4/saxmappings/"];
    [ma addObject:@"/usr/share/sope-4.4/saxmappings/"];
    searchPathes = [ma copy];
    
    if ([searchPathes count] == 0)
      NSLog(@"%s: no search pathes were found!", __PRETTY_FUNCTION__);
  }
  return searchPathes;
}

+ (id)modelWithName:(NSString *)_name {
  NSFileManager *fileManager;
  NSEnumerator  *pathes;
  NSString      *path;

  /* first look in main bundle */
  
  if ((path = [[NSBundle mainBundle] pathForResource:_name ofType:@"xmap"]))
    return [self modelWithContentsOfFile:path];

  /* then in Library */
  
  fileManager = [NSFileManager defaultManager];
  pathes      = [[[self class] saxMappingSearchPathes] objectEnumerator];
  _name       = [_name stringByAppendingPathExtension:@"xmap"];
  
  while ((path = [pathes nextObject])) {
    BOOL isDir;
    
    path = [path stringByAppendingPathComponent:_name];
    
    if (![fileManager fileExistsAtPath:path isDirectory:&isDir])
      continue;
    if (isDir)
      continue;
    
    break;
  }
  
  return [self modelWithContentsOfFile:path];
}

+ (id)modelWithContentsOfFile:(NSString *)_path {
  NSDictionary *dict;
  
  if ((dict = [NSDictionary dictionaryWithContentsOfFile:_path]) == nil)
    return nil;
  
  return [[[self alloc] initWithDictionary:dict] autorelease];
}

- (id)initWithDictionary:(NSDictionary *)_dict {
  self->nsToModel =
    [mapDictsToObjects(_dict, [SaxNamespaceModel class]) retain];
  return self;
}

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

/* queries */

- (SaxTagModel *)modelForTag:(NSString *)_localName namespace:(NSString *)_ns {
  SaxNamespaceModel *nsmap;
  
  if ((nsmap = [self->nsToModel objectForKey:_ns]) == nil) {
    if ((nsmap = [self->nsToModel objectForKey:@"*"]) == nil)
      return nil;
  }
  return [nsmap modelForTag:_localName];
}

/* faking dictionary */

- (id)objectForKey:(id)_key {
  return [self->nsToModel objectForKey:_key];
}

@end /* SaxMappingModel */

@implementation SaxNamespaceModel

- (id)initWithDictionary:(NSDictionary *)_dict {
  self->tagToModel = [mapDictsToObjects(_dict, [SaxTagModel class]) retain];
  return self;
}

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

/* queries */

- (SaxTagModel *)modelForTag:(NSString *)_localName {
  SaxTagModel *map;
  
  if ((map = [self->tagToModel objectForKey:_localName]))
    return map;
  if ((map = [self->tagToModel objectForKey:@"*"]))
    return map;
  return nil;
}

/* faking dictionary */

- (id)objectForKey:(id)_key {
  return [self->tagToModel objectForKey:_key];
}

@end /* SaxNamespaceModel */

@implementation SaxTagModel

- (NSDictionary *)_extractAttributeMapping:(NSDictionary *)as {
  NSMutableDictionary *md;
  NSEnumerator *keys;
  NSString     *k;
  NSDictionary *result;
      
  md = [[NSMutableDictionary alloc] initWithCapacity:16];
      
  keys = [as keyEnumerator];
  while ((k = [keys nextObject])) {
    id val;
	
    val = [as objectForKey:k];
	
    if ([val isKindOfClass:[NSString class]])
      [md setObject:val forKey:k];
    else if ([val count] == 0)
      [md setObject:k forKey:k];
    else 
      [md setObject:[(NSDictionary *)val objectForKey:@"key"] forKey:k];
  }
  
  result = [md copy];
  [md release];
  return result;
}

- (id)initWithDictionary:(NSDictionary *)_dict {
  if ((self = [super init])) {
    NSDictionary *rels;
    NSDictionary *as;
    
    self->className     = [[_dict objectForKey:@"class"]  copy];
    self->key           = [[_dict objectForKey:@"key"]    copy];
    self->tagKey        = [[_dict objectForKey:@"tagKey"] copy];
    self->namespaceKey  = [[_dict objectForKey:@"namespaceKey"] copy];
    self->parentKey     = [[_dict objectForKey:@"parentKey"] copy];
    self->contentKey    = [[_dict objectForKey:@"contentKey"] copy];
    self->defaultValues = [[_dict objectForKey:@"defaultValues"] copy];
    
    if ((as = [_dict objectForKey:@"attributes"]))
      self->attrToKey = [self _extractAttributeMapping:as];
    
    if ((rels = [_dict objectForKey:@"ToManyRelationships"])) {
      NSMutableDictionary *md;
      NSEnumerator *keys;
      NSString *k;
      
      self->toManyRelationshipKeys = [[rels allKeys] copy];
    
      md = [[NSMutableDictionary alloc] initWithCapacity:16];
      
      keys = [self->toManyRelationshipKeys objectEnumerator];
      while ((k = [keys nextObject])) {
	id       tags;
	NSString *tag;
	
	tags = [rels objectForKey:k];
	if ([tags isKindOfClass:[NSString class]])
	  tags = [NSArray arrayWithObject:tags];
	tags = [tags objectEnumerator];
	
	while ((tag = [tags nextObject])) {
	  NSString *t;
	  
	  if ((t = [md objectForKey:tag])) {
	    NSLog(@"SaxObjectModel: cannot map tag '%@' to key '%@', "
		  @"it is already mapped to key '%@'.",
		  tag, k, t);
	  }
	  else {
	    [md setObject:k forKey:tag];
	  }
	}
      }
      self->tagToKey = [md copy];
      [md release];
    }
    
  }
  return self;
}

- (void)dealloc {
  [self->defaultValues          release];
  [self->toManyRelationshipKeys release];
  [self->tagToKey     release];
  [self->className    release];
  [self->tagKey       release];
  [self->namespaceKey release];
  [self->parentKey  release];
  [self->contentKey release];
  [self->key        release];
  [self->attrToKey  release];
  [super dealloc];
}

/* accessors */

- (NSString *)className {
  return self->className;
}
- (NSString *)key {
  return self->key;
}
- (NSString *)tagKey {
  return self->tagKey;
}
- (NSString *)namespaceKey {
  return self->namespaceKey;
}
- (NSString *)parentKey {
  return self->parentKey;
}
- (NSString *)contentKey {
  return self->contentKey;
}

- (NSDictionary *)defaultValues {
  return self->defaultValues;
}

- (BOOL)isToManyKey:(NSString *)_key {
  return [self->toManyRelationshipKeys containsObject:_key];
}
- (NSArray *)toManyRelationshipKeys {
  return self->toManyRelationshipKeys;
}

- (BOOL)isToManyTag:(NSString *)_tag {
  return ([self->tagToKey objectForKey:_tag] != nil) ? YES : NO;
}

- (NSString *)propertyKeyForChildTag:(NSString *)_tag {
  return [self->tagToKey objectForKey:_tag];
}

- (NSArray *)attributeKeys {
  return [self->attrToKey allKeys];
}
- (NSString *)propertyKeyForAttribute:(NSString *)_attr {
  return [self->attrToKey objectForKey:_attr];
}

/* object operations */

- (void)addValue:(id)_value toPropertyWithKey:(NSString *)_key 
  ofObject:(id)_object
{
  NSString *selname;
  SEL      sel;
  
  selname = [[NSString alloc] initWithFormat:@"addTo%@:", 
			      [_key capitalizedString]];
  if ((sel = NSSelectorFromString(selname)) == NULL) {
    if (doDebug) {
      NSLog(@"got no selector for key '%@', selname '%@' !",
	    _key, selname);
    }
  }
  [selname release]; selname = nil;
  
  if (doDebug) {
    NSLog(@"%s: adding value %@ to %@ of %@", __PRETTY_FUNCTION__,
	  _value, _key, _object);
    NSLog(@"  selector: %@", NSStringFromSelector(sel));
  }
  
  if ((sel != NULL) && [_object respondsToSelector:sel]) {
    [_object performSelector:sel withObject:_value];
  }
  else {
    id v;
    
    v = [_object valueForKey:_key];
    
    if ([self isToManyKey:_key]) {
      /* to-many relationship */

      if (v == nil) {
        [_object takeValue:[NSArray arrayWithObject:_value] forKey:_key];
      }
      else {
#if NeXT_Foundation_LIBRARY || COCOA_Foundation_LIBRARY || \
    APPLE_FOUNDATION_LIBRARY
        if ([v isKindOfClass:NSCFArrayClass] &&
            _CFArrayIsMutable((CFArrayRef)v))
#else
        if ([v respondsToSelector:@selector(addObject:)])
#endif
	  /* the value is mutable */
          [v addObject:_value];
        else {
	  /* the value is immutable */
          v = [v arrayByAddingObject:_value];
          [_object takeValue:v forKey:_key];
        }
      }
    }
    else {
      NSLog(@" APPLIED ON TO-ONE (%@) !", _key);
      /* to-one relationship */
      if (v != _value)
        [_object takeValue:v forKey:_key];
    }
  }
}

@end /* SaxTagModel */
