/* 
   EOModel.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/NSBundle.h>
#include <Foundation/NSValue.h>
#include <Foundation/NSUtilities.h>
#include <Foundation/NSObjCRuntime.h>

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

#include <eoaccess/common.h>
#include <eoaccess/EOModel.h>
#include <eoaccess/EOEntity.h>
#include <eoaccess/EOFault.h>
#include <eoaccess/EOGenericRecord.h>

/* Include the code from EOKeyValueCoding.m and EOCustomValues.m here because
   they contain categories to various classes from Foundation that don't get
   linked into the client application since no one refers them. The NeXT linker
   knows how to deal with this but all the other linkers don't... */
#include "EOKeyValueCoding.m"
#include "EOCustomValues.m"

@implementation EOModel

+ (NSString*)findPathForModelNamed:(NSString*)modelName
{
    int i;
    NSBundle* bundle = [NSBundle mainBundle];
    NSString* modelPath = [bundle pathForResource:modelName ofType:@"eomodel"];
    NSString* paths[] = { @"~/Library/Models",
			  @"/LocalLibrary/Models",
			  @"/NextLibrary/Models",
			  nil };

    if(modelPath)
	return modelPath;

    for(i = 0; paths[i]; i++) {
	bundle = [NSBundle bundleWithPath:paths[i]];
	modelPath = [bundle pathForResource:modelName ofType:@"eomodel"];
	if(modelPath)
	    return modelPath;
    }
    return nil;
}

- init
{
    [entities release];
    [entitiesByName release];
    [entitiesByClassName release];

    entities = [GCArray new];
    entitiesByName = [GCMutableDictionary new];
    entitiesByClassName = [GCMutableDictionary new];

    return self;
}

- (void)dealloc
{
    /* Release only the non garbage collectable objects */
    [version release];
    [name release];
    [path release];
    [adaptorName release];
    [adaptorClassName release];	
    [connectionDictionary release];
    [userDictionary release];
	[docComment release];
    [super dealloc];
}

- (void)gcDecrementRefCountOfContainedObjects
{
    [(id)entities gcDecrementRefCount];
    [(id)entitiesByName gcDecrementRefCount];
    [(id)entitiesByClassName gcDecrementRefCount];
}

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

    [(id)entities gcIncrementRefCount];
    [(id)entitiesByName gcIncrementRefCount];
    [(id)entitiesByClassName gcIncrementRefCount];

    [(id)entities gcIncrementRefCountOfContainedObjects];
    [(id)entitiesByName gcIncrementRefCountOfContainedObjects];
    [(id)entitiesByClassName gcIncrementRefCountOfContainedObjects];

    return YES;
}

- initWithContentsOfFile:(NSString*)filename
{
    NSString* propList;

    [self init];
    path = [filename retain];
    propList = [[[NSString alloc] initWithContentsOfFile:path] autorelease];
    return [self initWithPropertyList:[propList propertyList]];
}

- initWithPropertyList:propertyList
{
    int i, count;
    NSArray* propListEntities;

    if(!propertyList)
	THROW([[InvalidArgumentException alloc]
	    initWithReason:@"EOModel: Argument of initWithPropertyList: must "
			   @"not be the nil object"]);

    if(![propertyList isKindOfClass:[NSDictionary class]])
	THROW([[InvalidArgumentException alloc]
	    initWithReason:@"EOModel: Argument of initWithPropertyList: must "
			   @"be kind of NSDictionary class"]);

    [self init];
    version = [[propertyList objectForKey:@"EOModelVersion"] retain];
    name = [[propertyList objectForKey:@"name"] retain];
    adaptorName = [[propertyList objectForKey:@"adaptorName"] retain];
    adaptorClassName = [[propertyList objectForKey:@"adaptorClassName"]
			    retain];
    if(!adaptorClassName)
	  adaptorClassName = [[NSString stringWithFormat:@"%@%@",adaptorName,@"Adaptor"] retain];
    connectionDictionary = [[propertyList objectForKey:@"connectionDictionary"]
				retain];
    userDictionary = [[propertyList objectForKey:@"userDictionary"] retain];
	docComment = [[propertyList objectForKey:@"docComment"] retain];
    propListEntities = [propertyList objectForKey:@"entities"];

    flags.errors = NO;
    [self setCreateMutableObjects:YES];
    count = [propListEntities count];
    for(i = 0; i < count; i++) {
	id entity = [EOEntity entityFromPropertyList:
				    [propListEntities objectAtIndex:i]
			      model:self];
	[self addEntity:entity];
    }

    count = [entities count];
    for(i = 0; i < count; i++)
	[[entities objectAtIndex:i] replaceStringsWithObjects];

    /* Init relationships */
    for(i = 0; i < count; i++) {
	EOEntity* entity = [entities objectAtIndex:i];
	NSArray* rels = [entity relationships];

	/* Replace all the strings in relationships. */
	[rels makeObjectsPerform:@selector(replaceStringsWithObjects)];
    }

    /* Another pass to allow properly initialization of flattened
	relationships. */
    for(i = 0; i < count; i++) {
	EOEntity* entity = [entities objectAtIndex:i];
	NSArray* rels = [entity relationships];

	[rels makeObjectsPerform:@selector(initFlattenedRelationship)];
    }

    /* Init attributes */
    for(i = 0; i < count; i++) {
	EOEntity* entity = [entities objectAtIndex:i];
	NSArray* attrs = [entity attributes];
	[attrs makeObjectsPerform:@selector(replaceStringsWithObjects)];
    }

    [self setCreateMutableObjects:NO];
    return flags.errors ? [self autorelease], nil : self;
}

- initWithName:(NSString*)_name
{
    [self init];
    [_name retain];
    [name release];
    name = _name;
    return self;
}

- modelAsPropertyList
{
    NSMutableDictionary* model = [[NSMutableDictionary new] autorelease];
    int i, count;

    [model setObject:[[NSNumber numberWithInt:[isa version]] stringValue]
	    forKey:@"EOModelVersion"];
    if(name)
	[model setObject:name forKey:@"name"];
    if(adaptorName)
	[model setObject:adaptorName forKey:@"adaptorName"];
    if(adaptorClassName)
	[model setObject:adaptorClassName forKey:@"adaptorClassName"];
    if(connectionDictionary)
	[model setObject:connectionDictionary forKey:@"connectionDictionary"];
    if(userDictionary)
	[model setObject:userDictionary forKey:@"userDictionary"];
    if(docComment)
	[model setObject:docComment forKey:@"docComment"];
    if(entities && (count = [entities count])) {
	id entitiesArray = [NSMutableArray arrayWithCapacity:count];

	[model setObject:entitiesArray forKey:@"entities"];
	for(i = 0; i < count; i++)
	    [entitiesArray addObject:
		    [[entities objectAtIndex:i] propertyList]];
    }

    return model;
}

- (BOOL)addEntity:(EOEntity*)entity
{
    NSString* entityName = [entity name];

    if([entitiesByName objectForKey:entityName])
	return NO;

    if([self createsMutableObjects])
	[(GCMutableArray*)entities addObject:entity];
    else {
	entities = [[[entities autorelease] mutableCopy] autorelease];
	[(GCMutableArray*)entities addObject:entity];
	entities = [entities copy];
    }

    [entitiesByName setObject:entity forKey:entityName];
    [entitiesByClassName setObject:entity forKey:[entity className]];
    [entity setModel:self];
    return YES;
}

- (void)removeEntityNamed:(NSString*)entityName
{
    if(entityName) {
	id entity = [entitiesByName objectForKey:entityName];

	if([self createsMutableObjects])
	    [(GCMutableArray*)entities removeObject:entity];
	else {
	    entities = [[entities autorelease] mutableCopy];
	    [(GCMutableArray*)entities removeObject:entity];
	    entities = [[entities autorelease] copy];
	}
	[entitiesByName removeObjectForKey:entityName];
	[entity setModel:nil];
    }
}

- (EOEntity*)entityNamed:(NSString*)entityName
{
    return [entitiesByName objectForKey:entityName];
}

- (NSArray*)referencesToProperty:property
{
    [self notImplemented:_cmd];
    return 0;
}

- (EOEntity*)entityForObject:object
{
    NSString* className;

    if([object isKindOfClass:[EOFault class]])
	return [EOFault entityForFault:object];

    if([object isKindOfClass:[EOGenericRecord class]])
	return [object entity];

    className = NSStringFromClass([object class]);

    return [entitiesByClassName objectForKey:className];
}

- (BOOL)incorporateModel:(EOModel*)model
{
    [self notImplemented:_cmd];
    return 0;
}

- (void)setAdaptorName:(NSString*)_adaptorName
{
    ASSIGN(adaptorName, _adaptorName);
}

- (void)setAdaptorClassName:(NSString*)_adaptorClassName
{
    ASSIGN(adaptorClassName, _adaptorClassName);
}

- (void)setConnectionDictionary:(NSDictionary*)_connectionDictionary
{
    ASSIGN(connectionDictionary, _connectionDictionary);
}

- (void)setUserDictionary:(NSDictionary*)_userDictionary
{
    ASSIGN(userDictionary, _userDictionary);
}

- (void)setDocComment:(NSString*)comment
{
    ASSIGN(docComment,comment);
}

- (NSString*)path		{ return path; }
- (NSString*)name		{ return name; }
- (NSString*)adaptorName	{ return adaptorName; }
- (NSString*)adaptorClassName	{ return adaptorClassName; }
- (NSArray*)entities		{ return entities; }
- (NSDictionary*)userDictionary	{ return userDictionary; }
- (NSString*)docComment { return docComment; }
- (NSDictionary*)connectionDictionary	{ return connectionDictionary; }

+ (int)version			{ return 1; }

@end /* EOModel */


@implementation EOModel (EOModelPrivate)

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

    flags.createsMutableObjects = flag;

    if(flags.createsMutableObjects)
	entities = [[entities autorelease] mutableCopy];
    else
	entities = [[entities autorelease] copy];
}

- (BOOL)createsMutableObjects	{ return flags.createsMutableObjects; }
- (void)errorInReading		{ flags.errors = YES; }

//We can sublass this method to log the error
- (void)errorInReadingWithError:(NSString*)error_
{
  NSLog(error_);
  [self errorInReading];
};

@end /* EOModel (EOModelPrivate) */

