/*
  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.
*/


#import <EOControl/EOClassDescription.h>
#import <EOControl/EOFetchSpecification.h>
#import <EOControl/EOQualifier.h>
#import <EOControl/EOSortOrdering.h>
#import <NGExtensions/EODataSource+NGExtensions.h>
#import <NGExtensions/NSObject+Logs.h>
#import <SaxObjC/SaxObjectDecoder.h>
#import <SaxObjC/SaxXMLReaderFactory.h>

#import "iCalObject.h"
#import "iCalCalendar.h"

#import "iCalDataSource.h"

@interface NSObject(suppressCapitalizedKeyWarning)
+ (void)suppressCapitalizedKeyWarning;
@end

@implementation iCalDataSource

// THREAD
static id<NSObject,SaxXMLReader> parser = nil;
static SaxObjectDecoder *sax = nil;

- (void)_setupGlobals {
  if (parser == nil ) {
    SaxXMLReaderFactory *factory = 
      [SaxXMLReaderFactory standardXMLReaderFactory];
    parser = [[factory createXMLReaderForMimeType:@"text/calendar"] retain];
    if (parser == nil)
      [self logWithFormat:@"ERROR: found no SAX driver for 'text/calendar'!"];
  }
  if (sax == nil && parser != nil) {
    NSBundle *bundle;
    NSString *p;
    
#if COCOA_Foundation_LIBRARY
    /* otherwise we get warning on "X-WR-TIMEZONE" etc */
    [NSClassFromString(@"NSKeyBinding") suppressCapitalizedKeyWarning];
#endif
    
    bundle = [NSBundle bundleForClass:[self class]];
    if ((p = [bundle pathForResource:@"NGiCal" ofType:@"xmap"]))
      sax = [[SaxObjectDecoder alloc] initWithMappingAtPath:p];
    else
      sax = [[SaxObjectDecoder alloc] initWithMappingNamed:@"NGiCal"];
    
    [parser setContentHandler:sax];
    [parser setErrorHandler:sax];
  }
}


- (id)initWithURL:(NSURL *)_url entityName:(NSString *)_ename {
  [self _setupGlobals];
  if ((self = [super init])) {
    self->url        = [_url copy];
    self->entityName = [_ename copy];
  }
  return self;
}
- (id)initWithPath:(NSString *)_path entityName:(NSString *)_ename {
  NSURL *lurl;
  
  lurl = [[[NSURL alloc] initFileURLWithPath:_path] autorelease];
  return [self initWithURL:lurl entityName:_ename];
}

- (id)initWithURL:(NSURL *)_url {
  return [self initWithURL:_url entityName:nil];
}
- (id)initWithPath:(NSString *)_path {
  return [self initWithPath:_path entityName:nil];
}
- (id)init {
  return [self initWithURL:nil];
}

- (void)dealloc {
  [self->fetchSpecification release];
  [self->url                release];
  [self->entityName         release];
  [super dealloc];
}

/* accessors */

- (void)setFetchSpecification:(EOFetchSpecification *)_fspec {
  if ([self->fetchSpecification isEqual:_fspec])
    return;
  
  ASSIGNCOPY(self->fetchSpecification, _fspec);

  [self postDataSourceChangedNotification];
}
- (EOFetchSpecification *)fetchSpecification {
  return self->fetchSpecification;
}

/* fetching */

- (id)_parseCalendar {
  id cal;
  
  if (parser == nil) {
    [self logWithFormat:@"ERROR: missing iCalendar parser!"];
    return nil;
  }
  if (sax == nil) {
    [self logWithFormat:@"ERROR: missing SAX handler!"];
    return nil;
  }
  
  [parser parseFromSource:self->url];
  cal = [sax rootObject];
  
  return cal;
}

- (NSArray *)objectsForEntityNamed:(NSString *)ename inCalendar:(id)_cal {
  if ([ename isEqualToString:@"vevent"])
    return [_cal events];
  if ([ename isEqualToString:@"vtodo"])
    return [_cal todos];
  if ([ename isEqualToString:@"vjournal"])
    return [_cal journals];
  if ([ename isEqualToString:@"vfreebusy"])
    return [_cal freeBusys];
  
  [self logWithFormat:@"unknown calendar entity '%@'", ename];
  return nil;
}

- (NSArray *)objectsFromCalendar:(id)_cal {
  NSString *ename;
  
  ename = [self->fetchSpecification entityName];
  if ([ename length] == 0)
    ename = self->entityName;

  if ([ename length] == 0)
    return [_cal allObjects];
  
  if ([_cal isKindOfClass:[NSDictionary class]]) {
    /*
      This happens with Mozilla Calendar which posts one vcalendar
      entry (with method 'PUBLISH') per event.
    */
    NSMutableArray *ma;
    NSArray  *calendars;
    unsigned i, count;
    
    if (![[(NSDictionary *)_cal objectForKey:@"tag"] 
	   isEqualToString:@"iCalendar"]) {
      [self logWithFormat:
	      @"ERROR: calendar (entity=%@) passed in as a dictionary: %@", 
	      _cal];
    }
    
    if ((calendars=[(NSDictionary *)_cal objectForKey:@"subcomponents"])==nil)
      return nil;

    count = [calendars count];
    ma = [NSMutableArray arrayWithCapacity:(count + 1)];
    
    for (i = 0; i < count; i++) {
      NSArray *objects;
      
      objects = [self objectsForEntityNamed:ename 
		      inCalendar:[calendars objectAtIndex:i]];
      if ([objects count] == 0)
	continue;
      
      [ma addObjectsFromArray:objects];
    }
    return ma;
  }
  
  return [self objectsForEntityNamed:ename inCalendar:_cal];
}

- (NSArray *)fetchObjects {
  NSAutoreleasePool *pool;
  NSArray *result;
  id calendar;

  pool = [[NSAutoreleasePool alloc] init];
  
  if ((calendar = [self _parseCalendar]) == nil)
    return nil;
  
  if (self->fetchSpecification == nil) {
    result = [[self objectsFromCalendar:calendar] shallowCopy];
  }
  else {
    NSMutableArray *ma;
    NSEnumerator   *e;
    EOQualifier *q;
    NSArray     *sort;
    NSArray     *objects;
    iCalObject  *object;
    
    /* get objects */
    
    objects = [self objectsFromCalendar:calendar];
    
    /* first filter using qualifier */
    
    ma = [NSMutableArray arrayWithCapacity:[objects count]];
    q  = [self->fetchSpecification qualifier];
    e  = [objects objectEnumerator];
    while ((object = [e nextObject])) {
      if (q) {
	if (![(id<EOQualifierEvaluation>)q evaluateWithObject:object])
	  continue;
      }
      
      [ma addObject:object];
    }
    
    /* now sort */
    
    if ((sort = [self->fetchSpecification sortOrderings]))
      [ma sortUsingKeyOrderArray:sort];

    result = [ma shallowCopy];
  }
  
  [pool release];
  
  return [result autorelease];
}

@end /* iCalDataSource */
