/* 
   EORelationship.m

   Copyright (C) 1996 Free Software Foundation, Inc.

   Author: Ovidiu Predescu <ovidiu@bx.logicnet.ro>
   Date: August 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 <Foundation/NSArray.h>
#include <Foundation/NSUtilities.h>

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

#include <eoaccess/common.h>
#include <eoaccess/EOModel.h>
#include <eoaccess/EOAttribute.h>
#include <eoaccess/EOEntity.h>
#include <eoaccess/EORelationship.h>
#include <eoaccess/EOJoin.h>
#include <eoaccess/EOExpressionArray.h>
#include <eoaccess/exceptions/EOFExceptions.h>

@implementation EORelationship

- init
{
    joins = [GCArray new];
    sourceAttributes = [GCArray new];
    destinationAttributes = [GCArray new];
    return self;
}

- initWithName:(NSString*)_name
{
    ASSIGN(name,_name);
    return self;
}

- (void)dealloc
{
    [name release];
    [definition release];
    [userDictionary release];
	[docComment release];
    [super dealloc];
}

- (void)gcDecrementRefCountOfContainedObjects
{
    [entity gcDecrementRefCount];
    [destinationEntity gcDecrementRefCount];
    [(id)joins gcDecrementRefCount];
    [(id)sourceAttributes gcDecrementRefCount];
    [(id)destinationAttributes gcDecrementRefCount];
    [(id)componentRelationships gcDecrementRefCount];
}

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

    [entity gcIncrementRefCount];
    [destinationEntity gcIncrementRefCount];
    [(id)joins gcIncrementRefCount];
    [(id)sourceAttributes gcIncrementRefCount];
    [(id)destinationAttributes gcIncrementRefCount];
    [(id)componentRelationships gcIncrementRefCount];

    [entity gcIncrementRefCountOfContainedObjects];
    [destinationEntity gcIncrementRefCountOfContainedObjects];
    [(id)joins gcIncrementRefCountOfContainedObjects];
    [(id)sourceAttributes gcIncrementRefCountOfContainedObjects];
    [(id)destinationAttributes gcIncrementRefCountOfContainedObjects];
    [(id)componentRelationships gcIncrementRefCountOfContainedObjects];

    return YES;
}

// This method should be here to let the library work with NeXT foundation
- (id)copy
{
    return [self retain];
}

// This method should be here to let the library work with NeXT foundation
- (id)copyWithZone:(NSZone*)zone_
{
  return [self retain];
}

// Is equal only if same name; used to make aliasing ordering stable
- (unsigned)hash
{
    return [name hash];
}

-(NSString*)description
{
  NSString* dscr=[NSString stringWithFormat:@"<%s %p - name=%@ entity=%@ destinationEntity=%@ definition=%@",
						   object_get_class_name(self),
						   (void*)self,
						   [self name],
						   [[self entity]name],
						   [[self destinationEntity]name],
						   [self definition]];
  dscr=[dscr stringByAppendingFormat:@" userDictionary=%@",
			 [self userDictionary]];
  dscr=[dscr stringByAppendingFormat:@" joins=%@",
			 [self joins]];
  dscr=[dscr stringByAppendingFormat:@" sourceAttributes=%@",
			 [self sourceAttributes]];
  dscr=[dscr stringByAppendingFormat:@" destinationAttributes=%@",
			 [self destinationAttributes]];
  dscr=[dscr stringByAppendingFormat:@" componentRelationships=%@",
			 [self componentRelationships]];
  dscr=[dscr stringByAppendingFormat:@" isCompound=%s isFlattened=%s isToMany=%s>",
			 ([self isCompound] ? "YES" : "NO"),
			 ([self isFlattened] ? "YES" : "NO"),
			 ([self isToMany] ? "YES" : "NO")];
  return dscr;
};

+ (BOOL)isValidName:(NSString*)_name
{
    return [EOEntity isValidName:_name];
}

- (BOOL)addJoin:(EOJoin*)join
{
    if([self isFlattened])
	return NO;

    if([sourceAttributes count]) {
	EOAttribute* sourceAttribute = [join sourceAttribute];
	EOAttribute* destinationAttribute = [join destinationAttribute];
	if(([sourceAttributes indexOfObject:sourceAttribute]
		    != NSNotFound)
	    && ([destinationAttributes indexOfObject:destinationAttribute]
		    != NSNotFound))
	    return NO;
#if 0
	// xxx Cannot do this until strings have been replaced
	// with objects
	if(! [[sourceAttribute entity] isEqual:
			[[sourceAttributes objectAtIndex:0] entity]]
	    || ![[destinationAttribute entity] isEqual:
			[[destinationAttributes objectAtIndex:0] entity]])
	    return NO;
#endif
    }

    if([self createsMutableObjects]) {
	[(GCMutableArray*)joins addObject:join];
	[(GCMutableArray*)sourceAttributes addObject:[join sourceAttribute]];
	[(GCMutableArray*)destinationAttributes addObject:
						[join destinationAttribute]];
    }
    else {
	joins = [[[joins autorelease] arrayByAddingObject:join] retain];
	sourceAttributes = [[[sourceAttributes autorelease]
				arrayByAddingObject:[join sourceAttribute]]
				retain];
	destinationAttributes = [[[destinationAttributes autorelease]
				    arrayByAddingObject:
					    [join destinationAttribute]]
				    retain];
    }
    [join setRelationship:self];
    return YES;
}

- (void)removeJoin:(EOJoin*)join
{
    if([self isFlattened])
	return;

    if([self createsMutableObjects]) {
	[(GCMutableArray*)joins removeObject:join];
	[(GCMutableArray*)sourceAttributes
		removeObject:[join sourceAttribute]];
	[(GCMutableArray*)destinationAttributes
		removeObject:[join destinationAttribute]];
    }
    else {
	joins = [[joins autorelease] mutableCopy];
	[(GCMutableArray*)joins removeObject:join];
	joins = [[joins autorelease] copy];

	sourceAttributes = [[sourceAttributes autorelease] mutableCopy];
	[(GCMutableArray*)sourceAttributes
		removeObject:[join sourceAttribute]];
	sourceAttributes = [[sourceAttributes autorelease] copy];

	destinationAttributes = [[destinationAttributes autorelease]
				    mutableCopy];
	[(GCMutableArray*)destinationAttributes
		removeObject:[join destinationAttribute]];
	destinationAttributes = [[destinationAttributes autorelease] copy];
    }
    [join setRelationship:nil];
}

- (void)setDefinition:(NSString*)def
{
    NSArray* defArray;
    int i, count;
    EOEntity* currentEntity;
    id relationship = nil;

    if(!def)
	THROW([InvalidArgumentException new]);

    if([def isNameOfARelationshipPath]) {
	defArray = [def componentsSeparatedByString:@"."];
	count = [defArray count];
	componentRelationships = [[GCMutableArray alloc]
					initWithCapacity:count];
	flags.isFlattened = YES;
	NS_DURING {
	    currentEntity = entity;
	    for(i = 0; i < count; i++) {
		id relationshipName = [defArray objectAtIndex:i];
    
		/* Take the address of `relationship' to force the compiler
		   to not allocate it into a register. */
		*(&relationship) = [currentEntity relationshipNamed:
						relationshipName];
		if(!relationship)
		    THROW([[InvalidPropertyException alloc]
				initWithName:relationshipName
				entity:currentEntity]);
		[componentRelationships addObject:relationship];
		flags.isToMany |= [relationship isToMany];
		currentEntity = [relationship destinationEntity];
	    }
	    if(destinationEntity && ![destinationEntity isEqual:currentEntity])
		THROW([[DestinationEntityDoesntMatchDefinitionException alloc]
			initForDestination:destinationEntity
			andDefinition:def
			relationship:self]);
	    ASSIGN(destinationEntity, currentEntity);
	}
	NS_HANDLER {
	    [componentRelationships release];
	    componentRelationships = nil;
	    [localException raise];
	}
	NS_ENDHANDLER
    }
    else
	THROW([[InvalidNameException alloc] initWithName:def]);

    ASSIGN(definition, def);
}

- (BOOL)setToMany:(BOOL)flag
{
    if([self isFlattened])
	return NO;
    flags.isToMany = flag;
    return YES;
}

- (BOOL)setName:(NSString*)_name
{
    if([entity referencesProperty:_name])
	return NO;
    ASSIGN(name, _name);
    return NO;
}

- (BOOL)isCompound
{
    return [joins count] > 1;
}

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

- (void)setEntity:(EOEntity*)_entity	{ ASSIGN(entity, _entity); }
- (void)setUserDictionary:(NSDictionary*)dict { ASSIGN(userDictionary, dict); }
- (void)setDocComment:(NSString*)comment
{
    ASSIGN(docComment,comment);
}

- (NSString*)name			{ return name; }
- (NSString*)definition			{ return definition; }
- (NSArray*)joins			{ return joins; }
- (NSArray*)sourceAttributes		{ return sourceAttributes; }
- (NSArray*)destinationAttributes	{ return destinationAttributes; }
- (EOEntity*)entity			{ return entity; }
- (EOEntity*)destinationEntity		{ return destinationEntity; }
- (BOOL)isFlattened			{ return flags.isFlattened; }
- (BOOL)isToMany			{ return flags.isToMany; }
- (NSDictionary*)userDictionary		{ return userDictionary; }
- (NSString*)docComment { return docComment; }
- (NSArray*)componentRelationships	{ return componentRelationships; }

- (BOOL)referencesProperty:property
{
    if([sourceAttributes indexOfObject:property] != NSNotFound)
	return YES;

    if([destinationAttributes indexOfObject:property] != NSNotFound)
	return YES;

    if([componentRelationships indexOfObject:property] != NSNotFound)
	return YES;

    return NO;
}

- (NSDictionary*)foreignKeyForRow:(NSDictionary*)row
{
    int j, i, n = [row count];
    id keys[n], vals[n];
    id key, val, fky;
    
    for (i = j = 0, n = [sourceAttributes count]; j < n; j++) {
	key = [(EOAttribute*)[sourceAttributes objectAtIndex:j] name];
	fky = [(EOAttribute*)[destinationAttributes objectAtIndex:j] name];
	val = [row objectForKey:key];
	if (val) {
	    vals[i] = val;
	    keys[i] = fky;
	    i++;
	}
    }
    
    return [[[NSDictionary alloc]
	initWithObjects:vals forKeys:keys count:i] autorelease];
}

@end /* EORelationship */


@implementation EORelationship (EORelationshipPrivate)

+ (EORelationship*)relationshipFromPropertyList:(id)propertyList
	model:(EOModel*)model
{
    EORelationship* relationship = [[EORelationship new] autorelease];
    NSArray* array;
    NSEnumerator* enumerator;
    id joinPList;

    [relationship setCreateMutableObjects:YES];
    [relationship setName:[propertyList objectForKey:@"name"]];
    [relationship setUserDictionary:
	    [propertyList objectForKey:@"userDictionary"]];
	[relationship setDocComment:[propertyList objectForKey:@"docComment"]];

    array = [propertyList objectForKey:@"joins"];
    enumerator = [array objectEnumerator];
    while((joinPList = [enumerator nextObject])) {
	id join = [EOJoin joinFromPropertyList:joinPList];
	if(![relationship addJoin:join]) {
	  NSString* _error=[NSString stringWithFormat:@"cannot add join with source attribute '%@' and destination"
								 @"attribute '%@' to relationship '%@'",
								 [join sourceAttribute], [join destinationAttribute],
								 [relationship name]];
	  [model errorInReadingWithError:_error];
	}
    }

    relationship->destinationEntity
	    = [[propertyList objectForKey:@"destination"] retain];

    relationship->flags.isToMany
	    = [[propertyList objectForKey:@"isToMany"] isEqual:@"Y"];

    /* Do not send here the -setDefinition: message because the relationships
       are not yet created from the model file. */
    relationship->definition = [[propertyList objectForKey:@"definition"]
				    retain];

    return relationship;
}

- (void)replaceStringsWithObjects
{
    id model = [entity model];
    int i, count;

    if(destinationEntity) {
	id destinationEntityName = [destinationEntity autorelease];
	destinationEntity = [[model entityNamed:destinationEntityName] retain];
	if(!destinationEntity) {
	  NSString* _error=[NSString stringWithFormat:@"invalid entity name '%@' specified as destination entity "
								 @"for relationship '%@' in entity '%@'",
								 destinationEntityName, name, [entity name]];
	  [model errorInReadingWithError:_error];
	}
    }

    if(!(destinationEntity || definition)) {
	  NSString* _error=[NSString stringWithFormat:@"relationship '%@' in entity '%@' is incompletely specified: "
								 @"no destination entity or definition.",
								 name, [entity name]];
	  [model errorInReadingWithError:_error];
    }

    if(definition && [joins count]) {
	  NSString* _error=[NSString stringWithFormat:@"relationship '%@' in entity '%@': flattened relationships "
								 @"cannot have joins", name, [entity name]];
	  [model errorInReadingWithError:_error];
    }

    count = [joins count];
    for(i = 0; i < count; i++) {
	EOAttribute* attribute;

	[[joins objectAtIndex:i] replaceStringsWithObjectsInRelationship:self];

	attribute = [entity attributeNamed:[sourceAttributes objectAtIndex:i]];
	if(attribute)
	    [(GCMutableArray*)sourceAttributes replaceObjectAtIndex:i
					       withObject:attribute];
	else {
	  NSString* _error=[NSString stringWithFormat:@"invalid source attribute '%@' in relationship '%@' "
								 @"of entity '%@'", [sourceAttributes objectAtIndex:i],
								 name, [entity name]];
	  [model errorInReadingWithError:_error];
	}

	attribute = [destinationEntity attributeNamed:
				[destinationAttributes objectAtIndex:i]];
	if(attribute)
	    [(GCMutableArray*)destinationAttributes replaceObjectAtIndex:i
						    withObject:attribute];
	else {
	  NSString* _error=[NSString stringWithFormat:@"invalid destination attribute '%@' in relationship '%@' "
								 @"of entity '%@'", [destinationAttributes objectAtIndex:i],
								 name, [entity name]];
	  [model errorInReadingWithError:_error];
	}
    }
    [self setCreateMutableObjects:NO];
}

- (void)initFlattenedRelationship
{
    if(definition) {
	TRY {
	    [self setDefinition:definition];
	} END_TRY
	CATCH(PropertyDefinitionException) {
	    [[entity model] errorInReadingWithError:[localException reason]];
	}
	END_CATCH
    }
}

- (id)propertyList
{
    id propertyList = [[NSMutableDictionary new] autorelease];
    int i, count;

    if(name)
	[propertyList setObject:name forKey:@"name"];
    if(definition)
	[propertyList setObject:definition forKey:@"definition"];
    if(userDictionary)
	[propertyList setObject:userDictionary forKey:@"userDictionary"];
    if(docComment)
	[propertyList setObject:docComment forKey:@"docComment"];

    if((count = [joins count])) {
	id joinsPList = [[NSMutableArray new] autorelease];
	for(i = 0; i < count; i++) {
	    id joinPList = [[joins objectAtIndex:i] propertyList];
	    [joinsPList addObject:joinPList];
	}
	[propertyList setObject:joinsPList forKey:@"joins"];
	[propertyList setObject:
		[(EOEntity*)[[sourceAttributes objectAtIndex:0] entity] name]
		      forKey:@"destination"];
    }

    if(![self isFlattened] && destinationEntity)
	[propertyList setObject:[destinationEntity name]
		      forKey:@"destination"];

    if(![self isFlattened])
	[propertyList setObject:
		    [NSString stringWithCString:(flags.isToMany ? "Y" : "N")]
		  forKey:@"isToMany"];

    return propertyList;
}

- (void)setCreateMutableObjects:(BOOL)flag
{
    if(flags.createsMutableObjects == flag)
	return;

    flags.createsMutableObjects = flag;

    if(flags.createsMutableObjects) {
	joins = [[joins autorelease] mutableCopy];
	sourceAttributes = [[sourceAttributes autorelease] mutableCopy];
	destinationAttributes = [[destinationAttributes autorelease]
				    mutableCopy];
    }
    else {
	joins = [[joins autorelease] copy];
	sourceAttributes = [[sourceAttributes autorelease] copy];
	destinationAttributes = [[destinationAttributes autorelease] copy];
    }
}

- (BOOL)createsMutableObjects	{ return flags.createsMutableObjects; }

@end /* EORelationship (EORelationshipPrivate) */
