/* 
   EODatabase.m

   Copyright (C) 1996 Free Software Foundation, Inc.

   Author: Mircea Oancea <mircea@jupiter.elcom.pub.ro>
   Date: 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/NSDictionary.h>
#include <Foundation/NSString.h>
#include <Foundation/NSException.h>
#include <Foundation/NSValue.h>
#include <Foundation/NSUtilities.h>

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

#include <eoaccess/EOAdaptor.h>
#include <eoaccess/EOModel.h>
#include <eoaccess/EOEntity.h>
#include <eoaccess/EOGenericRecord.h>

#include "EODatabase.h"
#include "EODatabaseContext.h"
#include "EOObjectUniquer.h"
#include "EOFault.h"

@implementation EODatabase

/*
 *  Database Global Methods
 */

static NSMutableArray* databaseInstances;

+ (void)initialize
{
    // THREAD
    databaseInstances = [[NSMutableArray alloc] init];
}

+ (void)makeAllDatabasesPerform:(SEL)aSelector withObject:anObject
{
    int i;
    
    // THREAD
    for (i = [databaseInstances count]-1; i >= 0; i--)
	[[[databaseInstances objectAtIndex:i] nonretainedObjectValue] 
	    performSelector:aSelector withObject:anObject];
}

/*
 * Initializing new instances
 */

- initWithAdaptor:(EOAdaptor*)anAdaptor
{
    if (!anAdaptor) {
	[self autorelease];
	return nil;
    }
    
    [databaseInstances addObject:[NSValue valueWithNonretainedObject:self]];
    adaptor = [anAdaptor retain];
    objectsDictionary = [[EOObjectUniquer alloc] init];
    contexts = [[NSMutableArray alloc] init];
    flags.isUniquingObjects = YES;
    flags.isKeepingSnapshots = YES;
    flags.isLoggingWarnings = YES;
    
    return self;
}

- initWithModel:(EOModel*)aModel
{
    return [self initWithAdaptor:[EOAdaptor adaptorWithModel:aModel]];
}

- (void)dealloc
{
  int i=0;
  for(i=[databaseInstances count]-1;i>=0;i--)
	{
	  NSValue* tmpValue=[databaseInstances objectAtIndex:i];
	  if ([tmpValue nonretainedObjectValue]==self)
		[databaseInstances removeObjectAtIndex:i];
	};
  [adaptor release];
  [objectsDictionary release];
  [contexts release];
  [super dealloc];
}

/* 
 * Getting the adaptor
 */

- (EOAdaptor*)adaptor
{
    return adaptor;
}

/*
 * Getting uniquer
 */

- (EOObjectUniquer*)objectUniquer
{
    return objectsDictionary;
}

/*
 * Checking connection status
 */

- (BOOL)hasBusyChannels
{
  int i=0;
  for (i = [contexts count]-1; i >= 0; i--)
	if ([[[contexts objectAtIndex:i] nonretainedObjectValue] 
		  hasBusyChannels])
	  return YES;
  return NO;
};

-(int)busyChannelsCount
{
  int i=0;
  int busyChannelsCount=0;
  for (i = [contexts count]-1; i >= 0; i--)
	busyChannelsCount+=[[[contexts objectAtIndex:i] nonretainedObjectValue] busyChannelsCount];
  return busyChannelsCount;
};

- (BOOL)hasOpenChannels
{
  int i;
    
  for (i = [contexts count]-1; i >= 0; i--)
	if ([[[contexts objectAtIndex:i] nonretainedObjectValue] 
		  hasOpenChannels])
	  return YES;
  return NO;
}

-(int)openChannelsCount
{
  int i=0;
  int openChannelsCount=0;
  for (i = [contexts count]-1; i >= 0; i--)
	openChannelsCount+=[[[contexts objectAtIndex:i] nonretainedObjectValue] openChannelsCount];
  return openChannelsCount;
};


/*
 * Getting the database contexts
 */

- createContext
{
    return [[[EODatabaseContext alloc] initWithDatabase:self] autorelease];
}

- (NSArray*)contexts
{
    int i, n;
    NSMutableArray* array = [NSMutableArray array];
    
    for (i=0, n=[contexts count]; i < n; i++)
	[array addObject:[[contexts objectAtIndex:i] nonretainedObjectValue]];
    
    return array;
}

- (void)contextDidInit:aContext
{
    [contexts addObject:[NSValue valueWithNonretainedObject:aContext]];
}

- (void)contextWillDealloc:aContext
{
    int i;
    
    for (i = [contexts count]-1; i >= 0; i--)
	if ([[contexts objectAtIndex:i] nonretainedObjectValue] == aContext) {
	    [contexts removeObjectAtIndex:i];
	    break;
	}
}

/*
 * Uniquing/snapshotting
 */

- (void)setUniquesObjects:(BOOL)yn
{
    if ([self hasOpenChannels])
	THROW([[InvalidArgumentException alloc] initWithFormat:
	    @"EODatabase:%x: All channels must be closed when changing "
	    @"uniquing mode in the EODatabase, "
	    @"in [EODatabase setUniquesObjects:]",
	    self]);

    if (yn == NO && flags.isUniquingObjects == YES)
	[objectsDictionary forgetAllObjects];
    flags.isUniquingObjects = yn;
}

- (BOOL)uniquesObjects
{
    return flags.isUniquingObjects;
}

- (void)setKeepsSnapshots:(BOOL)yn
{
    if ([self hasOpenChannels])
	THROW([[InvalidArgumentException alloc] initWithFormat:
	    @"EODatabase:%x: All channels must be closed when changing "
	    @"snapshoting mode in the EODatabase, "
	    @"in [EODatabase setKeepsSnapshots:]",
	    self]);

    if (yn == NO && flags.isKeepingSnapshots == YES)
	[objectsDictionary forgetAllSnapshots];
    flags.isKeepingSnapshots = yn;
}

- (BOOL)keepsSnapshots
{
    return flags.isKeepingSnapshots;
}

/*
 * Handle Objects
 */

- (void)forgetAllObjects
{
    [objectsDictionary forgetAllObjects];
}

+ (void)forgetObject:anObj
{
    [self makeAllDatabasesPerform:_cmd withObject:anObj];
}

- (void)forgetObject:anObj
{
    [objectsDictionary forgetObject:anObj];
}

- (void)forgetAllSnapshots
{
    [objectsDictionary forgetAllSnapshots];
}

- objectForPrimaryKey:(NSDictionary*)aKey
  entity:(EOEntity*)anEntity
{
    if (flags.isUniquingObjects && aKey && anEntity) {
	aKey = [anEntity primaryKeyForRow:aKey];
	if (!aKey)
	    return nil;
	
	return [objectsDictionary objectForPrimaryKey:aKey entity:anEntity];
    }
    return nil;
}

- (NSDictionary*)snapshotForObject:anObj
{
    EOUniquerRecord* rec = [objectsDictionary recordForObject:anObj];
    
    return rec ? rec->snapshot : nil;
}

- (NSDictionary*)primaryKeyForObject:anObj
{
    EOUniquerRecord* rec = [objectsDictionary recordForObject:anObj];
    
    return rec ? rec->pkey : nil;
}

- (void)primaryKey:(NSDictionary**)aKey
  andSnapshot:(NSDictionary**)aSnapshot
  forObject:anObj
{
    EOUniquerRecord* rec = [objectsDictionary recordForObject:anObj];
    if (rec) {
	if (aKey)
	    *aKey = rec->pkey;
	if (aSnapshot)
	    *aSnapshot = rec->snapshot;
    }
    else {
	if (aKey)
	    *aKey = nil;
	if (aSnapshot)
	    *aSnapshot = nil;
    }
}

- (void)recordObject:anObj
  primaryKey:(NSDictionary*)aKey
  snapshot:(NSDictionary*)aSnapshot
{
    EOEntity* entity;
    
    if ([anObj respondsToSelector:@selector(entity)])
	entity = [anObj entity];
    else
	entity = [[adaptor model] entityForObject:anObj];
    
    [self recordObject:anObj 
	primaryKey:aKey entity:entity snapshot:aSnapshot];
}

- (void)recordObject:anObj
  primaryKey:(NSDictionary*)aKey
  entity:(EOEntity*)anEntity
  snapshot:(NSDictionary*)aSnapshot
{
    if (!anObj)
	THROW([[InvalidArgumentException alloc] initWithFormat:
	    @"EODatabase:%x: Cannot record null object, "
	    @"in [EODatabase recordObject:primaryKey:entity:snapshot:]",
	    self]);
    if (!anEntity && flags.isUniquingObjects)
	THROW([[InvalidArgumentException alloc] initWithFormat:
	    @"EODatabase:%x: Cannot record object with null entity "
	    @"when the database is uniquing objects, "
	    @"in [EODatabase recordObject:primaryKey:entity:snapshot:]",
	    self]);
    aKey = [anEntity primaryKeyForRow:aKey];
    if (!aKey && flags.isUniquingObjects)
	THROW([[InvalidArgumentException alloc] initWithFormat:
	    @"EODatabase:%x: Cannot record object with null key "
	    @"when the database is uniquing objects, "
	    @"in [EODatabase recordObject:primaryKey:entity:snapshot:]",
	    self]);
    if (!aSnapshot && flags.isKeepingSnapshots)
	THROW([[InvalidArgumentException alloc] initWithFormat:
	    @"EODatabase:%x: Cannot record object with null snapshot "
	    @"when the database is keeping snapshots, "
	    @"in [EODatabase recordObject:primaryKey:entity:snapshot:]",
	    self]);

    [objectsDictionary recordObject:anObj 
	primaryKey:flags.isUniquingObjects ? aKey : nil
	entity:flags.isUniquingObjects ? anEntity : nil
	snapshot:flags.isKeepingSnapshots ? aSnapshot : nil];
}

- (BOOL)isObject:anObj updatedOutsideContext:(EODatabaseContext*)aContext
{
    int i;
    
    for (i = [contexts count]-1; i >= 0; i--) {
	id ctx = [[contexts objectAtIndex:i] nonretainedObjectValue];
	
	if ( (ctx != aContext) && ([ctx isObjectUpdated:anObj]) )
	    return YES;
    }
    return NO;
}

/*
 * Error messages
 */
 
- (BOOL)logsErrorMessages
{
    return flags.isLoggingWarnings;
}

- (void)setLogsErrorMessages:(BOOL)yn
{
    flags.isLoggingWarnings = yn;
}

- (void)reportErrorFormat:(NSString*)format, ...
{
    va_list va;
    
    va_start(va, format);
    [self reportErrorFormat:format arguments:va];
    va_end(va);
}

- (void)reportErrorFormat:(NSString*)format arguments:(va_list)arguments
{
    [self reportError:[[[NSString alloc] initWithFormat:format 
	arguments:arguments] autorelease]];
}

- (void)reportError:(NSString*)error
{
    if (flags.isLoggingWarnings)
	NSLog(@"EODatabase:%x:%s", self, [error cString]);
}

@end /* EODatabase */

