/*
   Project: UL

   Copyright (C) 2005 Michael Johnston & Jordi Villa-Freixa

   Author: Michael Johnston

   Created: 2005-07-12 15:24:33 +0200 by michael johnston

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

   This application 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 General Public
   License along with this library; if not, write to the Free
   Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111 USA.
*/

#include "ULFramework/ULDatabaseInterface.h"

static id databaseInterface;

@implementation ULDatabaseInterface

+ (id) databaseInterface
{
	//check if we have already created the databaseInterface

	if(databaseInterface != nil)
		return databaseInterface;
	else
	{
		databaseInterface = [self new];
		return databaseInterface;
	}
}

- (BOOL) _isAvailableSQLDatabaseBackend
{
	NSBundle *sqlBackendBundle;
	
	//check if the AdunSQLDatabase bundle is available

	sqlBackendBundle = [NSBundle bundleWithPath: 
				[NSHomeDirectory() stringByAppendingPathComponent:
				@"GNUstep/Library/Bundles/ULSQLDatabaseBackend.bundle"]];
	if(sqlBackendBundle == nil)
		return NO;
	else
		return YES;

}

- (void) _loadSQLDatabaseBackend
{
	NSBundle *sqlBackendBundle;

	sqlBackendBundle = [NSBundle bundleWithPath: 
				[NSHomeDirectory() stringByAppendingPathComponent:
				@"GNUstep/Library/Bundles/ULSQLDatabaseBackend.bundle"]];


	if(ULSQLDatabaseBackend = [sqlBackendBundle principalClass])
		NSDebugLLog(@"ULDatabaseInterface", @"Found SQL backend bundle.\n");
	else
		[NSException raise: NSInternalInconsistencyException 
			format: @"Specified plugin has no principal class"];
}

- (void) _initSQLBackends
{
	//Removed for 0.6
}

- (id) init
{
	id name;

	if(databaseInterface != nil)
		return databaseInterface;

	if(self = [super init])
	{
		//loadup the necessary backends - use sqlclient to create 
		//a connection to available database and then use that
		//connection to initialise the backend

		backends = [NSMutableDictionary new];
		fileSystemBackend = [ULFileSystemDatabaseBackend new];
		[backends setObject: fileSystemBackend forKey: [fileSystemBackend clientName]];
		availableClients = [NSMutableArray new];
		[availableClients addObject: [fileSystemBackend clientName]];
		databaseInterface = self;

		//Disabling backend bundle loading for UL0.52
	/*	if([self _isAvailableSQLDatabaseBackend])
		{
			NSLog(@"SQLDatabase Bundle Available");
			[self _loadSQLDatabaseBackend];
			[self _initSQLBackends];
		}
		else
		{
			NSWarnLog(@"AdunSQLDatabase bundle not available.");
			NSWarnLog(@"SQL backend support disabled");
		
		}*/
			
		//register for the various notification from the backends

		[[NSNotificationCenter defaultCenter] addObserver: self
			selector: @selector(_handleBackendNotification:)
			name: @"ULDatabaseBackendDidAddObjectNotification"
			object: nil];
		[[NSNotificationCenter defaultCenter] addObserver: self
			selector: @selector(_handleBackendNotification:)
			name: @"ULDatabaseBackendAdditionFailedNotification"
			object: nil];
		[[NSNotificationCenter defaultCenter] addObserver: self
			selector: @selector(_handleBackendNotification:)
			name: @"ULDatabaseBackendConnectionDidDieNotification"
			object: nil];
	}

	return self;
}

- (void) dealloc
{
	databaseInterface = nil;
	[fileSystemBackend release];
	[backends release];
	[availableClients release];
	[super dealloc];
}

//FIXME: Implement all notification handling.
//Also have to add notifications for failure to update metadata or output references
//due to a connection failing (Clients must be able to handle the above situations aswell).
- (void) _handleBackendNotification: (NSNotification*) aNotification
{
	if([[aNotification name] isEqual: @"ULDatabaseBackendDidAddObjectNotification"])
	{
		[[NSNotificationCenter defaultCenter]
				postNotificationName: @"ULDatabaseInterfaceDidAddObjectNotification"
				object: self];
	}
	else if([[aNotification name] isEqual: @"ULDatabaseBackendAdditionFailedNotification"])
	{
		NSWarnLog(@"Recieved %@ notification - Handling not implemented",
			[aNotification name]);	
	}
	else if([[aNotification name] isEqual: @"ULDatabaseBackendConnectionDidDieNotification"])
	{
		NSWarnLog(@"Recieved %@ notification - Handling not implemented",
			[aNotification name]);	
	}

	//FIXME: handle connection failures and addition failures
}			

- (id) backendForClient: (NSString*) clientName
{
	return [backends objectForKey: clientName];
}

- (void) addObject: (id) object 
		toSchema: (NSString*) schema
		ofClient: (id) clientName
{
	backend = [self backendForClient: clientName];
	[backend addObject: object toSchema: schema];
}

- (void) addObjectToFileSystemDatabase: (id) object
{
	[fileSystemBackend addObject: object toSchema: nil];
}

- (BOOL) objectInFileSystemDatabase: (id) object
{
	return [fileSystemBackend objectInDatabase: object];
}

- (void) updateMetadataForObject: (id) object 
		inSchema: (NSString*) schema
		ofClient: (NSString*) clientName
{
	backend = [self backendForClient: clientName];
	[backend updateMetadataForObject: object inSchema: schema];
}

- (void) updateOutputReferencesForObject: (id) object
{
	id client;

	client = [object valueForVolatileMetadataKey: @"DatabaseClient"];
	if(client == nil)
		[NSException raise: NSInternalInconsistencyException
			format: @"Object has no DatabaseClient attribute. Cannot update output refereneces"];

	backend = [self backendForClient: client];
	[backend updateOutputReferencesForObject: object];
}

- (void) removeObjectOfClass: (id) className 
		withID: (NSString*) ident
		fromSchema: (NSString*) schema
		ofClient: (NSString*) clientName
{
	backend = [self backendForClient: clientName];
	[backend removeObjectOfClass: className withID: ident fromSchema: schema];
}

- (void) removeObjectsOfClass: (id) className 	
		withIDs: (NSArray*) idents
		fromSchema: (NSString*) schema
		ofClient: (NSString*) clientName
{
	backend = [self backendForClient: clientName];
	[backend removeObjectsOfClass: className withIDs: idents fromSchema: schema];
}

- (id) unarchiveObjectWithID: (NSString*) ident 
	ofClass: (NSString*) className 
	fromSchema: (NSString*) schema
	ofClient: (NSString*) clientName
{
	backend = [self backendForClient: clientName];
	return [backend unarchiveObjectWithID: ident ofClass: className fromSchema: schema];
}

- (NSArray*) availableObjectsOfClass: (NSString*) className
		inSchema: (NSString*) schema
		ofClient: (NSString*) clientName
{
	id retval;

	backend = [self backendForClient: clientName];
	retval = [backend availableObjectsOfClass: className inSchema: schema];
	return retval;
}

- (NSArray*) contentTypeInformationForSchema: (NSString*) schema
		ofClient: (NSString*) clientName
{
	backend = [self backendForClient: clientName];
	return [backend contentTypeInformationForSchema: schema];
}

- (id) metadataForObjectWithID: (NSString*) ident 
	ofClass: (NSString*)  className
	inSchema: (NSString*) schema
	ofClient: (NSString*) clientName
{
	backend = [self backendForClient: clientName];
	return [backend metadataForObjectWithID: ident 
			ofClass: className
			inSchema: schema];
}

- (id) outputReferencesForObjectWithID: (NSString*) ident 
	ofClass: (NSString*) className
	inSchema: (NSString*) schema
	ofClient: (NSString*) clientName
{
	backend = [self backendForClient: clientName];
	[backend outputReferencesForObjectWithID: ident 
		ofClass: className
		inSchema: schema];
}

- (id) inputReferencesForObjectWithID: (NSString*) ident 
	ofClass: (NSString*)  className
	inSchema: (NSString*) schema
	ofClient: (NSString*) clientName
{
	backend = [self backendForClient: clientName];
	[backend inputReferencesForObjectWithID: 
		ident ofClass: className
		inSchema: schema];
}

//have to handle unavailable databases here
//FIXME: Only works with file system database at the moment.

- (void) removeOutputReferencesToObjectWithID: ident
		ofClass: (id) className
		inSchema: (NSString*) schema
		ofClient: (NSString*) clientName;
{
	NSEnumerator *inputReferencesEnum;
	id inputReference;

	//The arguements input references are the objects we are looking for.
	inputReferencesEnum = [[self inputReferencesForObjectWithID: ident
				ofClass: className
				inSchema: schema
				ofClient: clientName] objectEnumerator];
	while(inputReference = [inputReferencesEnum nextObject])
	{
		[fileSystemBackend removeOutputReferenceToObjectWithID: ident
			fromObjectWithID: [inputReference objectForKey: @"Identification"]
			ofClass: [inputReference objectForKey: @"Class"]
			inSchema: [inputReference objectForKey: @"Schema"]];
	}
}

- (NSArray*) availableClients
{
	return availableClients;
}

- (NSArray*) schemaInformationForClient: (NSString*) clientName
{
	backend = [self backendForClient: clientName];
	return [backend schemaInformation];
}

- (NSString*) userForClient: (NSString*) clientName
{
	backend = [self backendForClient: clientName];
	return [backend user];
}

- (NSString*) databaseForClient: (NSString*) clientName
{
	backend = [self backendForClient: clientName];
	return [backend database];
}

- (void) saveDatabase
{
	NSEnumerator* backendsEnum = [backends objectEnumerator];

	while(backend = [backendsEnum nextObject])
		[backend saveDatabase];
}

- (id) createFileSystemStorageForSimulation: (id) object
{
	return [fileSystemBackend createStorageForSimulation: object];
}

@end
