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

@interface EOQualifier(EvalContext)
- (BOOL)evaluateWithObject:(id)_object inEvalContext:(id)_ctx;
@end

@implementation EOOrQualifier

//static BOOL debugEval      = NO;
static BOOL debugTransform = NO;

- (id)initWithQualifierArray:(NSArray *)_qualifiers {
  self->count      = [_qualifiers count];
  self->qualifiers = [_qualifiers copyWithZone:[self zone]];
  return self;
}

- (id)initWithQualifiers:(EOQualifier *)_qual1, ... {
  va_list va;
  EOQualifier *q;
  id *qs;
  unsigned c;
  NSArray *a;
  
  va_start(va, _qual1);
  for (c = 0, q = _qual1; q != nil; q = va_arg(va, id), c++)
    ;
  va_end(va);

  if (c == 0)
    return [self initWithQualifierArray:nil];

  qs = calloc(c, sizeof(id));
  
  va_start(va, _qual1);
  for (c = 0, q = _qual1; q != nil; q = va_arg(va, id), c++) {
    qs[c] = q;
  }
  va_end(va);

  a = [NSArray arrayWithObjects:qs count:c];
  free(qs);
  
  return [self initWithQualifierArray:a];
}

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

/* accessors */

- (NSArray *)qualifiers {
  return self->qualifiers;
}
- (NSArray *)subqualifiers {
  return [self qualifiers];
}

/* bindings */

- (EOQualifier *)qualifierWithBindings:(NSDictionary *)_bindings
  requiresAllVariables:(BOOL)_reqAll
{
  NSArray  *array;
  id       objects[self->count + 1];
  unsigned i;

  IMP objAtIdx;

  objAtIdx = [self->qualifiers methodForSelector:@selector(objectAtIndex:)];
  
  for (i = 0; i < self->count; i++) {
    id q, newq;

    q = objAtIdx(self->qualifiers, @selector(objectAtIndex:), i);
    newq = [q qualifierWithBindings:_bindings requiresAllVariables:_reqAll];
    if (newq == nil) newq = q;
    
    objects[i] = newq;
  }

  array = [NSArray arrayWithObjects:objects count:self->count];
  return [[[[self class] alloc] initWithQualifierArray:array] autorelease];
}

- (NSArray *)bindingKeys {
  NSMutableSet *keys = nil;
  unsigned i;
  IMP objAtIdx;

  objAtIdx = [self->qualifiers methodForSelector:@selector(objectAtIndex:)];
  
  for (i = 0; i < self->count; i++) {
    NSArray *qb;
    id q;

    q = objAtIdx(self->qualifiers, @selector(objectAtIndex:), i);
    qb = [q bindingKeys];

    if ([qb count] > 0) {
      if (keys == nil) keys = [NSMutableSet setWithCapacity:16];
      [keys addObjectsFromArray:qb];
    }
  }
  
  return keys ? [keys allObjects] : (NSArray *)[NSArray array];
}

/* keys */

- (void)addQualifierKeysToSet:(NSMutableSet *)_keys {
  /* new in WO 4.5 */
  [self->qualifiers makeObjectsPerformSelector:_cmd withObject:_keys];
}

/* evaluation */

- (BOOL)evaluateWithObject:(id)_object inEvalContext:(id)_ctx {
  unsigned i;
  IMP objAtIdx;
  
  if ((_ctx == nil) && (self->count > 1))
    _ctx = [NSMutableDictionary dictionaryWithCapacity:16];
  
  objAtIdx = [self->qualifiers methodForSelector:@selector(objectAtIndex:)];
  
  for (i = 0; i < self->count; i++) {
    id q;
    
    q = objAtIdx(self->qualifiers, @selector(objectAtIndex:), i);
    if ([q evaluateWithObject:_object inEvalContext:_ctx])
      return YES;
  }
  return NO;
}
- (BOOL)evaluateWithObject:(id)_object {
  return [self evaluateWithObject:_object inEvalContext:nil];
}

/* NSCoding */

- (void)encodeWithCoder:(NSCoder *)_coder {
  [_coder encodeObject:self->qualifiers];
}
- (id)initWithCoder:(NSCoder *)_coder {
  self->qualifiers = [[_coder decodeObject] retain];
  return self;
}

/* Comparing */

- (BOOL)isEqualToQualifier:(EOQualifier *)_qual {
  return [self->qualifiers isEqualToArray:[(EOOrQualifier *)_qual qualifiers]];
}

/* remapping keys */

- (EOQualifier *)qualifierByApplyingTransformer:(id)_transformer
  inContext:(id)_ctx
{
  if ([_transformer respondsToSelector:
                      @selector(transformOrQualifier:inContext:)]) {
    if (debugTransform)
      NSLog(@"transformer: %@\n  transform: %@", _transformer, self);
    return [_transformer transformOrQualifier:self inContext:_ctx];
  }
  else {
    EOOrQualifier *aq;
    NSArray  *a;
    id       *qs;
    unsigned i;
    BOOL     didTransform = NO;
    
    if (debugTransform) {
      NSLog(@"EOOrQualifier: transform %i using %@ ...", 
            self->count, _transformer);
    }
    
    qs = calloc(self->count + 1, sizeof(id));
    for (i = 0; i < self->count; i++) {
      EOQualifier *q;
      
      q     = [self->qualifiers objectAtIndex:i];
      qs[i] = [q qualifierByApplyingTransformer:_transformer inContext:_ctx];
      if (qs[i] == nil) 
        qs[i] = q;
      else if (qs[i] != q) {
        if (debugTransform)
          NSLog(@"EOOrQualifier:   subqualifier %i did transform", i);
        didTransform = YES;
      }
      else if (debugTransform)
        NSLog(@"EOOrQualifier:   subqualifier %i did not transform", i);
    }
    if (didTransform) {
      a = [[NSArray alloc] initWithObjects:qs count:self->count];
      if (qs) free(qs);
      aq = [[EOOrQualifier alloc] initWithQualifierArray:a];
      [a release];
      return [aq autorelease];
    }
    else {
      if (qs) free(qs);
      return [[self retain] autorelease];
    }
  }
}

- (EOQualifier *)qualifierByApplyingKeyMap:(NSDictionary *)_map {
  EOOrQualifier *aq;
  NSArray  *a;
  id       *qs;
  unsigned i;
  
  qs = calloc(self->count + 1, sizeof(id));
  for (i = 0; i < self->count; i++) {
    EOQualifier *q;
    
    q     = [self->qualifiers objectAtIndex:i];
    qs[i] = [q qualifierByApplyingKeyMap:_map];
    if (qs[i] == nil) qs[i] = q;
  }
  a = [[NSArray alloc] initWithObjects:qs count:self->count];
  if (qs) free(qs);
  aq = [[EOOrQualifier alloc] initWithQualifierArray:a];
  [a release];
  return [aq autorelease];
}

/* key/value archiving */

- (id)initWithKeyValueUnarchiver:(EOKeyValueUnarchiver *)_unarchiver {
  if ((self = [super initWithKeyValueUnarchiver:_unarchiver]) != nil) {
    self->qualifiers = [[_unarchiver decodeObjectForKey:@"qualifiers"] copy];
    self->count      = [self->qualifiers count];
  }
  return self;
}
- (void)encodeWithKeyValueArchiver:(EOKeyValueArchiver *)_archiver {
  [super encodeWithKeyValueArchiver:_archiver];
  [_archiver encodeObject:[self qualifiers] forKey:@"qualifiers"];
}

/* description */

- (NSString *)description {
  NSMutableString *ms;
  NSArray  *sd;
  unsigned i, len;
  
  sd = [self->qualifiers valueForKey:@"qualifierDescription"];
  if ((len = [sd count]) == 0)
    return nil;
  if (len == 1)
    return [sd objectAtIndex:0];
  
  ms = [NSMutableString stringWithCapacity:(len * 16)];
  [ms appendString:@"("];
  for (i = 0; i < len; i++) {
    if (i != 0) [ms appendString:@" OR "];
    [ms appendString:[sd objectAtIndex:i]];
  }
  [ms appendString:@")"];
  return ms;
}

@end /* EOOrQualifier */
