/* 
   EOQualifier.m

   Copyright (C) 1996 Free Software Foundation, Inc.

   Author: Ovidiu Predescu <ovidiu@bx.logicnet.ro>
   Date: September 1996

   This file is part of the GNUstep Database Library.

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library General Public
   License as published by the Free Software Foundation; either
   version 2 of the License, or (at your option) any later version.

   This library 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
   Library General Public License for more details.

   You should have received a copy of the GNU Library General Public
   License along with this library; see the file COPYING.LIB.
   If not, write to the Free Software Foundation,
   59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/

#include <stdio.h>

#include <Foundation/NSDictionary.h>
#include <Foundation/NSSet.h>
#include <Foundation/NSUtilities.h>

#include <extensions/NSException.h>
#include <extensions/DefaultScannerHandler.h>
#include <extensions/PrintfFormatScanner.h>
#include <extensions/exceptions/GeneralExceptions.h>

#include <eoaccess/common.h>
#include <eoaccess/EOEntity.h>
#include <eoaccess/EOAttribute.h>
#include <eoaccess/EORelationship.h>
#include <eoaccess/EOQualifier.h>
#include <eoaccess/EOKeyValueCoding.h>
#include <eoaccess/EOExpressionArray.h>
#include <eoaccess/EOJoin.h>
#include <eoaccess/EOSQLExpression.h>
#include <eoaccess/EOAdaptor.h>
#include <eoaccess/exceptions/EOFExceptions.h>

@interface EOQualifierScannerHandler : DefaultScannerHandler
{
    EOEntity* entity;
}
- (void)setEntity:(EOEntity*)entity;
@end

@implementation EOQualifierScannerHandler

- init
{
    [super init];

    specHandler['d'] = [self methodForSelector:@selector(convertInt:scanner:)];
    specHandler['f']
	    = [self methodForSelector:@selector(convertFloat:scanner:)];
    specHandler['s']
	    = [self methodForSelector:@selector(convertCString:scanner:)];
    specHandler['A']
	    = [self methodForSelector:@selector(convertProperty:scanner:)];
    specHandler['@']
	    = [self methodForSelector:@selector(convertObject:scanner:)];
    return self;
}

- (void)setEntity:(EOEntity*)_entity
{
    ASSIGN(entity, _entity);
}

- (void)dealloc
{
    [entity release];
    [super dealloc];
}

- (NSString*)convertInt:(va_list*)pInt scanner:(FormatScanner*)scanner
{
    char buffer[256];
    sprintf(buffer, [scanner currentSpecifier], va_arg(*pInt, int));
    return [NSString stringWithCString:buffer];
}

- (NSString*)convertFloat:(va_list*)pFloat scanner:(FormatScanner*)scanner
{
    char buffer[256];
    sprintf(buffer, [scanner currentSpecifier], va_arg(*pFloat, double));
    return [NSString stringWithCString:buffer];
}

- (NSString*)convertCString:(va_list*)pString scanner:(FormatScanner*)scanner
{
    char* string = va_arg(*pString, char*);
    return string ? [NSString stringWithCString:string] : @"";
}

- (NSString*)convertProperty:(va_list*)pString scanner:(FormatScanner*)scanner
{
    NSString* propertyName = va_arg(*pString, id);
    id property = [entity propertyNamed:propertyName];

    if(!property)
	THROW([[InvalidPropertyException alloc]
		    initWithName:propertyName entity:entity]);
    return propertyName;
}

- (NSString*)convertObject:(va_list*)pId scanner:scanner
{
    id object = va_arg(*pId, id);
    return [object expressionValueForContext:nil];
}

@end /* EOQualifierScannerHandler */


@interface EOQualifierJoinHolder : NSObject
{
    EOJoinOperator joinOperator;
    EOJoinSemantic joinSemantic;
    id source;
    id destination;
}
+ valueForJoinOperator:(EOJoinOperator)operator
  semantic:(EOJoinSemantic)semantic
  source:source
  destination:destination;
- (NSString*)expressionValueForContext:(EOSQLExpression*)context;
- source;
- destination;
@end

@implementation EOQualifierJoinHolder
+ valueForJoinOperator:(EOJoinOperator)operator
  semantic:(EOJoinSemantic)semantic
  source:_source
  destination:_destination;
{
    EOQualifierJoinHolder* value = [[self new] autorelease];

    value->joinOperator = operator;
    value->joinSemantic = semantic;
    value->source = [_source retain];
    value->destination = [_destination retain];
    return value;
}

-(void)dealloc
{
  DESTROY(source);
  DESTROY(destination);
  [super dealloc];
};

- (NSString*)expressionValueForContext:(EOSQLExpression*)context
{
    NSMutableString* result = [[NSMutableString new] autorelease];
    EOAdaptor* adaptor = [context adaptor];

    if ([source isKindOfClass:[EOAttribute class]])
	[result appendString:[source expressionValueForContext:context]];
    else {
	NSAssert([destination isKindOfClass:[EOAttribute class]],
		 @"either one of source or destination should be EOAttribute");
	[result appendString:
		    [adaptor formatValue:source forAttribute:destination]];
    }

    [result appendString:@" "];
    [result appendString:
	[context joinExpressionForOperator:joinOperator
		 semantic:joinSemantic]];
    [result appendString:@" "];

    if ([destination isKindOfClass:[EOAttribute class]])
	[result appendString:[destination expressionValueForContext:context]];
    else {
	NSAssert([source isKindOfClass:[EOAttribute class]],
		 @"either one of source or destination should be EOAttribute");
	[result appendString:
		    [adaptor formatValue:destination forAttribute:source]];
    }

    return result;
}

- source		{ return source; }
- destination		{ return destination; }

-(NSString*)description
{
  NSString* dscr=nil;
  NSString* joinOperatorDescr=nil;
  NSString* joinSemanticDescr=nil;
  switch(joinOperator)
	{
	case EOJoinEqualTo:
	  joinOperatorDescr=@"EOJoinEqualTo";
	  break;
	case EOJoinNotEqualTo:
	  joinOperatorDescr=@"EOJoinNotEqualTo";
	  break;
	case EOJoinGreaterThan:
	  joinOperatorDescr=@"EOJoinGreaterThan";
	  break;
	case EOJoinGreaterThanOrEqualTo:
	  joinOperatorDescr=@"EOJoinGreaterThanOrEqualTo";
	  break;
	case EOJoinLessThan:
	  joinOperatorDescr=@"EOJoinLessThan";
	  break;
	case EOJoinLessThanOrEqualTo:
	  joinOperatorDescr=@"EOJoinLessThanOrEqualTo";
	  break;
	};
  switch(joinSemantic)
	{
	case EOInnerJoin:
	  joinSemanticDescr=@"EOInnerJoin";
	  break;
	case EOFullOuterJoin:
	  joinSemanticDescr=@"EOFullOuterJoin";
	  break;
	case EOLeftOuterJoin:
	  joinSemanticDescr=@"EOLeftOuterJoin";
	  break;
	case EORightOuterJoin:
	  joinSemanticDescr=@"EORightOuterJoin";
	  break;
	};
  dscr=[NSString stringWithFormat:@"<%s %p - joinOperator=%@ joinSemantic=%@ source=%@ destination=%@>",
				 object_get_class_name(self),
				 (void*)self,
				 joinOperatorDescr,
				 joinSemanticDescr,
				 source,
				 destination];
  return dscr;
};
@end


@implementation EOQualifier

+ (EOQualifier*)qualifierForRow:(NSDictionary*)row
  entity:(EOEntity*)_entity
{
    EOQualifier* qualifier = [[EOQualifier new] autorelease];
    id enumerator = [row keyEnumerator];
    id attributeName, attribute, value;
    BOOL first = YES;

    while((attributeName = [enumerator nextObject])) {
	attribute = [_entity attributeNamed:attributeName];
	value = [row objectForKey:attributeName];

	if (!value || !attribute)
	    /* return nil when is unable to build a qualifier for all keys
	       in the given row
	    */
	    return nil;

	if (first)
	    first = NO;
	else
	    [qualifier->content addObject:@" AND "];

	[qualifier->content addObject:
	    [EOQualifierJoinHolder valueForJoinOperator:EOInnerJoin
				    semantic:EOJoinEqualTo
				    source:attribute
				    destination:value]];
    }

    qualifier->entity = [_entity retain];
    [qualifier _computeRelationshipPaths];

    return qualifier;
}

+ (EOQualifier*)qualifierForPrimaryKey:(NSDictionary*)dictionary
  entity:(EOEntity*)_entity
{
    NSDictionary* pkey = [_entity primaryKeyForRow:dictionary];
    
    /* return nil when is unable to build a complete qualifier
       for all primary key attributes
    */
    return pkey ? [self qualifierForRow:pkey entity:_entity] : nil;
}

+ (EOQualifier*)qualifierForRow:(NSDictionary*)row 
  relationship:(EORelationship*)relationship
{
    NSArray* componentRelationships = [relationship componentRelationships];
    EOQualifier* qualifier = [[EOQualifier new] autorelease];
    NSArray* sourceAttributes;
    id tmpRelationship = relationship;
    EOAttribute *sourceAttribute, *destinationAttribute;
    id value;
    EOJoin* join;
    NSArray* joins;
    BOOL first = YES;
    int i, j, count, count2;

    /* Make a qualifier string in the following manner. If the relationship is
       not flattened we must join using the join operator the values from `row'
       and the foreign keys taken from the destination entity of relatioship.
       If the relationship is flattend we must append then joins between the
       components of relationship. */

    if (componentRelationships) {
	tmpRelationship = [componentRelationships objectAtIndex:0];
	sourceAttributes = [tmpRelationship sourceAttributes];
    }
    else
	sourceAttributes = [relationship sourceAttributes];

    joins = [tmpRelationship joins];
    count = [sourceAttributes count];
    NSAssert2 (count == [joins count], @"different number of joins and source "
	       @"attributes: joins = %d, sourceAttributes = %d",
	       [joins count], count);

    for (i = 0; i < count; i++) {
	if (first)
	    first = NO;
	else
	    [qualifier->content addObject:@" AND "];

	join = [joins objectAtIndex:i];
	sourceAttribute = [join sourceAttribute];
	value = [row objectForKey:[sourceAttribute name]];
	if (!value)
	    /* Returns nil if `row' does not contain all the values needed to 
	       create a complete qualifier
	    */
	    return nil;

	destinationAttribute = [join destinationAttribute];
	[qualifier->content addObject:
	    [EOQualifierJoinHolder valueForJoinOperator:[join joinOperator]
				    semantic:[join joinSemantic]
				    source:destinationAttribute
				    destination:value]];
    }

    if (componentRelationships) {
	EOEntity* tempEntity = [tmpRelationship destinationEntity];

	/* The relationship is flattened. Iterate over the components and 
	   add joins that `link' the components between them.
	*/
	count2 = [componentRelationships count];
	for (j = 1; j < count2; j++) {
	    relationship = [componentRelationships objectAtIndex:j];
	    joins = [relationship joins];
	    sourceAttributes = [relationship sourceAttributes];
	    for (i = 0, count = [sourceAttributes count]; i < count; i++) {
		[qualifier->content addObject:@" AND "];
		join = [joins objectAtIndex:i];
		[qualifier->content addObject:
		    [EOQualifierJoinHolder valueForJoinOperator:
						[join joinOperator]
					    semantic:[join joinSemantic]
					    source:[join sourceAttribute]
					    destination:
						[join destinationAttribute]]];
	    }
	}

	/* Here we make a hack because later we need to use this qualifier in
	   a SELECT expression in which the qualifier's entity should be the
	   final destination entity of the flattened relationship. In addition
	   we need in the FROM clause all the entities corresponding to the
	   components of the relationship to be able to insert the joins
	   between the values given in row and the final attributes from the
	   destination entity of the last component of relationship. */
	ASSIGN(qualifier->entity, tempEntity);
	[qualifier _computeRelationshipPaths];
	ASSIGN(qualifier->entity, [relationship destinationEntity]);
	return qualifier;
    }
    else {
	ASSIGN(qualifier->entity, [relationship destinationEntity]);
	return qualifier;
    }
}

+(EOQualifier*)qualifierForRelationship:(EORelationship*)relationship
{
  NSArray* componentRelationships = [relationship componentRelationships];
  EOQualifier* qualifier = [[EOQualifier new] autorelease];
  int relationshipIndex=0;
  int relationshipsCount=0;
  
  if (!componentRelationships)
	componentRelationships=[NSArray arrayWithObject:relationship];
  
  relationshipsCount = [componentRelationships count];
  for (relationshipIndex=0;relationshipIndex<relationshipsCount; relationshipIndex++)
	{
	  id tmpRelationship = [componentRelationships objectAtIndex:relationshipIndex];
	  NSArray* joins = [tmpRelationship joins];
	  NSArray* sourceAttributes = [tmpRelationship sourceAttributes];
	  int joinIndex=0;
	  int joinsCount=[sourceAttributes count];
	  for (joinIndex=0;joinIndex<joinsCount;joinIndex++)
		{
		  EOJoin* join = [joins objectAtIndex:joinIndex];
		  if (relationshipIndex>0 || joinIndex>0)
			[qualifier->content addObject:@" AND "];
		  [qualifier->content addObject:[EOQualifierJoinHolder valueForJoinOperator:[join joinOperator]
															   semantic:[join joinSemantic]
															   source:[join sourceAttribute]
															   destination:[join destinationAttribute]]];
		};
	};
  ASSIGN(qualifier->entity,[relationship entity]);
  [qualifier _computeRelationshipPaths];
//  ASSIGN(qualifier->entity, [relationship destinationEntity]);
  return qualifier;
}

+ (EOQualifier*)qualifierForObject:sourceObject 
  relationship:(EORelationship*)relationship
{
    NSArray* sourceAttributes = [relationship sourceAttributes];
    int i, count = [sourceAttributes count];
    NSMutableArray* attrNames = [NSMutableArray arrayWithCapacity:count];

    for (i = 0; i < count; i++)
	[attrNames addObject:
		    [(EOAttribute*)[sourceAttributes objectAtIndex:i] name]];

    return [self qualifierForRow:[sourceObject valuesForKeys:attrNames]
		 relationship:relationship];
}

- init
{
    ASSIGN(content, [EOExpressionArray new]);
    ASSIGN(relationshipPaths, [NSMutableSet new]);
    ASSIGN(additionalEntities, [NSMutableSet new]);
    return self;
}

- initWithEntity:(EOEntity*)_entity 
  qualifierFormat:(NSString*)qualifierFormat, ...
{
    va_list ap;
    id formatScanner;
    id scannerHandler;
    NSString* qualifierString;

    [self init];
    ASSIGN(entity, _entity);

    if (!qualifierFormat)
	return self;

    formatScanner = [[PrintfFormatScanner new] setAllowOnlySpecifier:YES];
    scannerHandler = [EOQualifierScannerHandler new];

    va_start(ap, qualifierFormat);
    [scannerHandler setEntity:_entity];
    [formatScanner setFormatScannerHandler:scannerHandler];
    qualifierString = [formatScanner stringWithFormat:qualifierFormat
				     arguments:ap];
    va_end(ap);

    [formatScanner release];
    [scannerHandler release];
    ASSIGN(content, [EOExpressionArray parseExpression:qualifierString
				    entity:entity
				    replacePropertyReferences:YES]);

    [self _computeRelationshipPaths];

    return self;
}

-(NSString*)description
{
  NSString* dscr=[NSString stringWithFormat:@"<%s %p - entity=%@ content=%@ usesDistinct=%s ",
						   object_get_class_name(self),
						   (void*)self,
						   [[self entity]name],
						   content,
						   ([self usesDistinct] ? "YES" : "NO")];

  dscr=[dscr stringByAppendingFormat:@"relationshipPaths=%@ additionalEntities=%@>",
			 [self relationshipPaths],
			 [self additionalEntities]];
  return dscr;
};

- (void)_computeRelationshipPaths
{
    int i, count;

    void handle_attribute (id object)
    {
	if([object isFlattened]) {
	    id definitionArray = [object definitionArray];
	    NSRange range = { 0, [definitionArray count] - 1 };
	    id relsArray = [definitionArray subarrayWithRange:range];

	    [relationshipPaths addObject:relsArray];
	    [additionalEntities addObjectsFromArray:relsArray];
	}
	else
	    [additionalEntities addObject:[object entity]];
    }

    [relationshipPaths removeAllObjects];

    for(i = 0, count = [content count]; i < count; i++) {
	id object = [content objectAtIndex:i];

	/* The objects from content can only be NSString, values or
	   EOAttribute. */
	if([object isKindOfClass:[EOAttribute class]]) {
	    handle_attribute (object);
	}
	else if ([object isKindOfClass:[EOQualifierJoinHolder class]]) {
	    id source = [object source];
	    id destination = [object destination];

	    if ([source isKindOfClass:[EOAttribute class]])
		handle_attribute (source);
	    if ([destination isKindOfClass:[EOAttribute class]])
		handle_attribute (destination);
	}
	else if([object isKindOfClass:[EORelationship class]])
	    THROW([[InvalidPropertyException alloc]
		    initWithFormat:@"cannot refer a EORelationship in a "
			    @"EOQualifier: '%@'",
			    [(EORelationship*)object name]]);
    }
}

- (void)dealloc
{
    [relationshipPaths release];
    [additionalEntities release];
    [super dealloc];
}

- (void)gcDecrementRefCountOfContainedObjects
{
    [entity gcDecrementRefCount];
    [(id)content gcDecrementRefCount];
}

- (BOOL)gcIncrementRefCountOfContainedObjects
{
    if(![super gcIncrementRefCountOfContainedObjects])
	return NO;

    [entity gcIncrementRefCount];
    [(id)content gcIncrementRefCount];

    [entity gcIncrementRefCountOfContainedObjects];
    [(id)content gcIncrementRefCountOfContainedObjects];

    return YES;
}

- copy
{
    return [self copyWithZone:NSDefaultMallocZone()];
}

- copyWithZone:(NSZone*)zone
{
    EOQualifier* copy = [isa new];

    copy->entity = [entity retain];
    copy->content = [content mutableCopy];
    copy->relationshipPaths = [relationshipPaths mutableCopy];
    copy->usesDistinct = usesDistinct;
    return copy;
}

- (void)negate
{
    [content insertObject:@"NOT (" atIndex:0];
    [content addObject:@")"];
}

- (void)conjoinWithQualifier:(EOQualifier*)qualifier
{
    if(![qualifier isKindOfClass:[EOQualifier class]])
	THROW([[InvalidArgumentException alloc]
		setReason:@"argument of conjoinWithQualifier: method must "
		    @"be EOQualifier"]);

    if(entity != qualifier->entity)
	THROW([[InvalidArgumentException alloc]
		setReason:@"qualifier argument of conjoinWithQualifier: "
		    @"must have the same entity as receiver"]);

    [content insertObject:@"(" atIndex:0];
    [content addObject:@") AND ("];
    [content addObjectsFromArray:qualifier->content];
    [content addObject:@")"];
    [relationshipPaths unionSet:qualifier->relationshipPaths];
}

- (void)disjoinWithQualifier:(EOQualifier*)qualifier
{
    if(![qualifier isKindOfClass:[EOQualifier class]])
	THROW([[InvalidArgumentException alloc]
		setReason:@"argument of disjoinWithQualifier: method must "
		    @"be EOQualifier"]);

    if(entity != qualifier->entity)
	THROW([[InvalidArgumentException alloc]
		setReason:@"qualifier argument of disjoinWithQualifier: "
		    @"must have the same entity as receiver"]);

    [content insertObject:@"(" atIndex:0];
    [content addObject:@") OR ("];
    [content addObjectsFromArray:qualifier->content];
    [content addObject:@")"];
    [relationshipPaths unionSet:qualifier->relationshipPaths];
}

- (EOEntity*)entity			{ return entity; }
- (BOOL)isEmpty				{ return entity == nil; }
- (void)setUsesDistinct:(BOOL)flag	{ usesDistinct = flag; }
- (BOOL)usesDistinct			{ return usesDistinct; }
- (NSMutableSet*)relationshipPaths	{ return relationshipPaths; }
- (NSMutableSet*)additionalEntities	{ return additionalEntities; }

- (NSString*)expressionValueForContext:(id<EOExpressionContext>)ctx
{
    return [content expressionValueForContext:ctx];
}

@end
