/*
  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 "imCommon.h"

@interface EOQualifier(PrivateMethodes)

- (NSString *)qualifierDescription;

- (NSException *)invalidImap4SearchQualifier:(NSString *)_reason;

- (NSException *)appendToImap4SearchString:(NSMutableString *)_search 
  insertNot:(BOOL)_insertNot;

@end

@implementation EOQualifier(IMAPAdditions)

- (BOOL)isImap4UnseenQualifier {
  return NO;
}

/* building search qualifiers */

static NSArray *FlagKeyWords = nil;
static NSArray *OtherKeyWords = nil;
static BOOL    debugOn = NO;

- (void)_initImap4SearchCategory {
  NSUserDefaults *ud;
  
  if (FlagKeyWords) return;

  ud = [NSUserDefaults standardUserDefaults];
  FlagKeyWords = [[NSArray alloc] initWithObjects: @"answered", @"deleted",
                            @"draft", @"flagged", @"new", @"old", @"recent",
                            @"seen", @"unanswered", @"undeleted", @"undraft",
                            @"unflagged", @"unseen", nil];
  OtherKeyWords = [[NSArray alloc] initWithObjects:
                             @"bcc", @"body", @"cc", @"from", @"subject",
                             @"text", @"to", @"keyword", @"unkeyword", nil];
  
  debugOn = [ud boolForKey:@"ImapDebugQualifierGeneration"];
}

- (NSException *)invalidImap4SearchQualifier:(NSString *)_reason {
  if (_reason == nil) _reason = @"unknown reason";
  return [NSException exceptionWithName:@"NGImap4SearchQualifierException"
                      reason:_reason
                      userInfo:nil];
}

- (NSEnumerator *)subqualifiersForImap4SearchString:(BOOL *)_isDisjunction {
  return nil;
}
- (BOOL)isImap4NotQualifier {
  return NO;
}
- (BOOL)isImap4KeyValueQualifier {
  return NO;
}

- (NSException *)appendToImap4SearchString:(NSMutableString *)_search 
  insertNot:(BOOL)_insertNot
{
  return [self invalidImap4SearchQualifier:@"expected key/value qualifier"];
}
- (NSException *)appendToImap4SearchString:(NSMutableString *)_search { 
  return [self appendToImap4SearchString:_search insertNot:NO];
}

- (id)imap4SearchString { /* returns exception on fail */
  // TODO: split up method
  BOOL            disjunction = NO; /* OR */
  NSEnumerator    *quals;
  id              qualifier;
  NSMutableString *search;
  
  [self _initImap4SearchCategory];
  
  if ([self isImap4UnseenQualifier]) {
    if (debugOn)
      [self logWithFormat:@"is unseen: %@ (%@)", self, [self class]];
    return @" unseen";
  }
  
  if (debugOn)
    [self logWithFormat:@"generate IMAP4 expression for qualifier: %@", self];
  
  search = [NSMutableString stringWithCapacity:256];
  quals  = nil;
  
  if ((quals = [self subqualifiersForImap4SearchString:&disjunction]) == nil) {
    if (debugOn)
      [self logWithFormat:@"  got no subqualifiers .."];
    
    return (id)[self invalidImap4SearchQualifier:@"unexpected qualifier 1"];
  }
  
  if (disjunction)
    [search appendString:@" or"];
  
  while ((qualifier = [quals nextObject])) {
    NSException *error;

    if (debugOn)
      [self logWithFormat:@"  append subqualifier: %@", qualifier];
    
    if ((error = [qualifier appendToImap4SearchString:search]))
      return error;
  }
  
  if (debugOn)
    [self logWithFormat:@"  generated: '%@'", search];

  return search;
}

@end /* EOQualifier(IMAPAdditions) */

@implementation EOAndQualifier(IMAPAdditions)

- (NSEnumerator *)subqualifiersForImap4SearchString:(BOOL *)_isDisjunction {
  if (_isDisjunction) *_isDisjunction = NO;
  return [[self qualifiers] objectEnumerator];
}

@end /* EOAndQualifier(IMAPAdditions) */

@implementation EOOrQualifier(IMAPAdditions)

- (NSEnumerator *)subqualifiersForImap4SearchString:(BOOL *)_isDisjunction {
  if (_isDisjunction) *_isDisjunction = YES;
  return [[self qualifiers] objectEnumerator];
}

@end /* EOOrQualifier(IMAPAdditions) */

@implementation EOKeyValueQualifier(IMAPAdditions)

- (NSEnumerator *)subqualifiersForImap4SearchString:(BOOL *)_isDisjunction {
  if (_isDisjunction) *_isDisjunction = NO;
  return [[NSArray arrayWithObject:self] objectEnumerator];
}

- (BOOL)isImap4KeyValueQualifier {
  return YES;
}

- (BOOL)isImap4UnseenQualifier {
  // TODO: this is rather weird: flags suggests an array value!
  if (![[self key] isEqualToString:@"flags"]) 
    return NO;
  return [[self value] isEqualToString:@"unseen"];
}

- (NSException *)appendFlagsCheckToImap4SearchString:(NSMutableString *)search 
  insertNot:(BOOL)insertNot
{
  NSEnumerator *enumerator = nil;
  id       lvalue;
  SEL      lselector;
  
  lvalue    = [self value];
  lselector = [self selector];
      
      if (sel_eq(lselector, EOQualifierOperatorEqual)) {
        lvalue = [NSArray arrayWithObject:lvalue];
      }
      else if (!sel_eq(lselector, EOQualifierOperatorContains)) {
        return [self invalidImap4SearchQualifier:
                       @"unexpected EOKeyValueQualifier selector"];
      }
      if (![lvalue isKindOfClass:[NSArray class]]) {
        return [self invalidImap4SearchQualifier:
                       @"expected an array in contains-qualifier"];
      }
      enumerator = [lvalue objectEnumerator];
      while ((lvalue = [enumerator nextObject])) {
        lvalue = [lvalue lowercaseString];
        
        if ([FlagKeyWords containsObject:lvalue]) {
	  [search appendString:insertNot ? @" not " : @" "];
          [search appendString:lvalue];
        }
        else {
          return [self invalidImap4SearchQualifier:
                         @"unexpected keyword for EOKeyValueQualifier"];
	}
      }
      return nil;
}

- (NSString *)imap4OperatorForDateComparisonSelector:(SEL)lselector {
  if (sel_eq(lselector, EOQualifierOperatorEqual))
    return @" senton ";
  if (sel_eq(lselector, EOQualifierOperatorGreaterThan))
    return @" sentsince ";
  if (sel_eq(lselector, EOQualifierOperatorLessThan))
    return @" sentbefore ";
  
  return nil;
}

- (NSException *)appendToImap4SearchString:(NSMutableString *)search 
  insertNot:(BOOL)insertNot
{
  /* returns exception on fail */
  NSString *lkey;
  id       lvalue;
  SEL      lselector;
  
  lkey      = [[self key] lowercaseString];
  lvalue    = [self value];
  lselector = [self selector];
    
  if ([lkey isEqualToString:@"flags"]) {
    return [self appendFlagsCheckToImap4SearchString:search 
                 insertNot:insertNot];
  }
  
  /* not a flag */
  if (insertNot) 
    [search appendString:@" not"];
  
  if ([lkey isEqualToString:@"date"]) {
    NSString *s;
    
    if (![lvalue isKindOfClass:[NSCalendarDate class]]) {
      return [self invalidImap4SearchQualifier:
		     @"expected a NSDate as value"];
    }
    
    if ((s = [self imap4OperatorForDateComparisonSelector:lselector]) == nil)
      return [self invalidImap4SearchQualifier:@"unexpected selector"];
    
    // TODO: much faster without descriptionWithCalendarFormat:?!
    s = [lvalue descriptionWithCalendarFormat:@"%d-%b-%Y"];
    [search appendString:s];
  }
  else if ([lkey isEqualToString:@"uid"]) {
    if (!sel_eq(lselector, EOQualifierOperatorEqual))
      return [self invalidImap4SearchQualifier:@"unexpected qualifier 2"];
    
    [search appendString:@" uid "];
    [search appendString:[lvalue stringValue]];
  }
  else if ([lkey isEqualToString:@"size"]) {
    if (sel_eq(lselector, EOQualifierOperatorGreaterThan))
      [search appendString:@" larger "];
    else if (sel_eq(lselector, EOQualifierOperatorLessThan))
      [search appendString:@" smaller "];
    else
      return [self invalidImap4SearchQualifier:@"unexpected qualifier 3"];
        
    [search appendString:[lvalue stringValue]];
  }
  else if ([OtherKeyWords containsObject:lkey]) {
    if (!sel_eq(lselector, EOQualifierOperatorEqual)) {
      [self logWithFormat:@"SELECTOR: got: %@, allowed: %@", 
	    NSStringFromSelector(lselector),
	    NSStringFromSelector(EOQualifierOperatorEqual)];
      return [self invalidImap4SearchQualifier:
		     @"unexpected qualifier, disallowed comparison on "
		     @"OtherKeyWords)"];
    }
    
    [search appendString:@" "];
    [search appendString:lkey];
    [search appendString:@" \""];
    [search appendString:[lvalue stringValue]];
    [search appendString:@"\""];
  }
  else {
    if (!sel_eq(lselector, EOQualifierOperatorEqual))
      return [self invalidImap4SearchQualifier:@"unexpected qualifier 5"];
    
    [search appendString:@" header "];
    [search appendString:lkey];
    [search appendString:@" \""];
    [search appendString:[lvalue stringValue]];
    [search appendString:@"\""];
  }
  return nil;
}

@end /* EOKeyValueQualifier(IMAPAdditions) */

@implementation EONotQualifier(IMAPAdditions)

- (NSEnumerator *)subqualifiersForImap4SearchString:(BOOL *)_isDisjunction {
  if (_isDisjunction) *_isDisjunction = NO;
  return [[NSArray arrayWithObject:self] objectEnumerator];
}

- (BOOL)isImap4NotQualifier {
  return YES;
}

- (NSException *)appendToImap4SearchString:(NSMutableString *)_search { 
  return [[self qualifier] appendToImap4SearchString:_search insertNot:YES];
}

@end /* EONotQualifier(IMAPAdditions) */
