/* 
   EOExpressionArray.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 <ctype.h>
#include <Foundation/NSString.h>
#include <Foundation/NSAutoreleasePool.h>
#include <Foundation/NSValue.h>
#include <Foundation/NSDate.h>

#include <extensions/objc-runtime.h>
#include <extensions/NSException.h>
#include <extensions/GarbageCollector.h>
#include <extensions/exceptions/GeneralExceptions.h>

#include <eoaccess/common.h>
#include <eoaccess/common.h>
#include <eoaccess/EOEntity.h>
#include <eoaccess/EOExpressionArray.h>
#include <eoaccess/EORelationship.h>

@implementation EOExpressionArray

+ (void)initialize
{
    static BOOL initialized = NO;

    if(!initialized) {
	initialized = YES;
	class_add_behavior(self, [GCArray class]);
    }
}

- init
{
    [super init];
    prefix = infix = suffix = nil;
    return self;
}

- initWithPrefix:(NSString*)_prefix
    infix:(NSString*)_infix
    suffix:(NSString*)_suffix
{
    [super init];
    ASSIGN(prefix, _prefix);
    ASSIGN(infix, _infix);
    ASSIGN(suffix, _suffix);
    return self;
}

- (void)dealloc
{
    [prefix release];
    [infix release];
    [suffix release];
    [super dealloc];
}

- (BOOL)referencesObject:(id)anObject
{
    return [self indexOfObject:anObject] != NSNotFound;
}

- (NSString*)expressionValueForContext:(id<EOExpressionContext>)ctx
{
    if(ctx && [self count] && 
	[[self objectAtIndex:0] isKindOfClass:[EORelationship class]])
	    return [ctx expressionValueForAttributePath:self];
    else {
	int i, count = [self count];
	id result = [[NSMutableString new] autorelease];
	SEL sel = @selector(appendString:);
	IMP imp = [result methodForSelector:sel];

	if(prefix)
	    [result appendString:prefix];

	if(count) {
	    (*imp)(result, sel, [[self objectAtIndex:0]
				    expressionValueForContext:ctx]);
	    for(i = 1 ; i < count; i++) {
		if (infix)
		    (*imp)(result, sel, infix);
		(*imp)(result, sel, [[self objectAtIndex:i]
					expressionValueForContext:ctx]);
	    }
	}

	if(suffix)
	    [result appendString:suffix];

	return result;
    }
}

- (void)setPrefix:(NSString*)_prefix		{ ASSIGN(prefix, _prefix); }
- (void)setInfix:(NSString*)_infix		{ ASSIGN(infix, _infix); }
- (void)setSuffix:(NSString*)_suffix		{ ASSIGN(suffix, _suffix); }

- (NSString*)prefix				{ return prefix; }
- (NSString*)infix				{ return infix; }
- (NSString*)suffix				{ return suffix; }

+ (EOExpressionArray*)parseExpression:(NSString*)expression
  entity:(EOEntity*)entity
  replacePropertyReferences:(BOOL)replacePropertyReferences
{
    EOExpressionArray* array = [[EOExpressionArray new] autorelease];
    const char* s = [expression cString];
    const char* start;
    id objectToken;
    id pool = [NSAutoreleasePool new];

    /* Divide the expression string in alternating substrings that obey the
       following simple grammar: 

	    I = [a-zA-Z0-9@_#]([a-zA-Z0-9@_.#$])+
	    O = \'.*\' | \".*\" | [^a-zA-Z0-9@_#]+
	    S -> I S | O S | nothing
    */
    while(*s) {
	/* Determines an I token. */
	if(isalnum(*s) || *s == '@' || *s == '_' || *s == '#') {
	    start = s;
	    for(++s; *s; s++)
		if(!isalnum(*s) && *s != '@' && *s != '_'
			&& *s != '.' && *s != '#' && *s != '$')
		    break;

	    objectToken = [NSString stringWithCString:start
				    length:(unsigned)(s - start)];

	    if(replacePropertyReferences) {
		id property = [entity propertyNamed:objectToken];

		if(property)
		    objectToken = property;
	    }
	    [array addObject:objectToken];
	}

	/* Determines an O token. */
	start = s;
	for(; *s && !isalnum(*s) && *s != '@' && *s != '_' && *s != '#'; s++) {
	    if(*s == '\'' || *s == '"') {
		char quote = *s;

		for(++s; *s; s++)
		    if(*s == quote)
			break;
		    else if(*s == '\\')
			s++; /* Skip the escaped characters */
		if(!*s)
		    THROW([[SyntaxErrorException alloc]
			    setReason:@"unterminated character string"]);
	    }
	}
	if(s != start) {
	    objectToken = [NSString stringWithCString:start
				    length:(unsigned)(s - start)];
	    [array addObject:objectToken];
	}
    }

    [pool release];
    return array;
}

@end /* EOExpressionArray */


@implementation NSObject (EOExpression)

- (NSString*)expressionValueForContext:(id<EOExpressionContext>)ctx
{
    if([self respondsToSelector:@selector(stringValue)])
	return [(id)self stringValue];
    else
	return [self description];
}

@end


@implementation NSString (EOExpression)

/* Avoid returning the description in case of NSString because if the string
   contains whitespaces it will be quoted. Particular adaptors have to override
   -formatValue:forAttribute: and they have to quote with the specific
   database character the returned string. */
- (NSString*)expressionValueForContext:(id<EOExpressionContext>)ctx
{
    return self;
}

@end
