/*
   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/ULFileSystemDatabaseBackend.h"

@implementation ULFileSystemDatabaseBackend

-(void) _createIndex: (NSString*) indexName inDirectory: (NSString*) dir
{
	ULDatabaseIndex *tempIndex;

	if([[dir lastPathComponent] isEqual: @"Simulations"])
		tempIndex = [[ULDatabaseSimulationIndex alloc] 
			initWithDirectory: dir];
	else
		tempIndex = [[ULDatabaseIndex alloc] initWithDirectory: dir];
	
	[NSKeyedArchiver archiveRootObject: tempIndex toFile: 
		[dir stringByAppendingPathComponent: indexName]];
	[tempIndex release];
}

- (void) _saveIndex: (ULDatabaseIndex*) index ofClass: (NSString*) className
{
	id file;
	NSKeyedArchiver* archiver;
	NSMutableData* data = [NSMutableData data];

	if([className isEqual: @"ULSystem"])
		file = [systemDir stringByAppendingPathComponent: @"SystemIndex"];
	else if([className isEqual: @"ULOptions"])
		file = [optionsDir stringByAppendingPathComponent: @"OptionsIndex"];
	else if([className isEqual: @"AdDataSet"])
		file = [dataSetDir stringByAppendingPathComponent: @"DataSetIndex"];
	else if([className isEqual: @"ULSimulation"])
		file = [simulationDir stringByAppendingPathComponent: @"SimulationIndex"];
	
	archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData: data];
	[archiver setOutputFormat: NSPropertyListXMLFormat_v1_0];
	[archiver encodeObject: index forKey: @"root"];
	[archiver finishEncoding];
	[data writeToFile: file atomically: NO];
	[archiver release];
}

- (id) _unarchiveIndexAtLocation: (NSString*) location
{
	id object;	

	if((object  = [NSKeyedUnarchiver unarchiveObjectWithFile: location]) == nil)
		[NSException raise: NSInvalidArgumentException 
			format: @"Decoding error. Could not locate index %@", location];

	return object;
}	

- (void) _checkForDirectory: (NSString*) path withIndex: (NSString*) indexName
{
	BOOL isDir;
	NSFileManager* fileManager = [NSFileManager defaultManager];

	if(!([fileManager fileExistsAtPath: path isDirectory: &isDir] && isDir))
	{
		[fileManager createDirectoryAtPath: path
			attributes: nil];
		[self _createIndex: indexName inDirectory: path];
		NSWarnLog(@"Database directory %@ was missing. Created it and the corresponding index",
			path);
	}		
}

- (void) _checkDatabaseSubdirectories
{
	[self _checkForDirectory: systemDir withIndex: @"SystemIndex"];
	[self _checkForDirectory: optionsDir withIndex: @"OptionsIndex"];
	[self _checkForDirectory: dataSetDir withIndex: @"DataSetIndex"];
	[self _checkForDirectory: simulationDir withIndex: @"SimulationIndex"];
}

- (void) _setWorkingEnvironment
{
	NSUserDefaults* userDefaults;

	databaseDir = [[ULIOManager appIOManager] databaseDir];
	[databaseDir retain];
	systemDir = [[databaseDir stringByAppendingPathComponent: @"Systems"] retain];
	optionsDir = [[databaseDir stringByAppendingPathComponent: @"Options"] retain];
	dataSetDir = [[databaseDir stringByAppendingPathComponent: @"DataSets"] retain];
	simulationDir = [[databaseDir stringByAppendingPathComponent: @"Simulations"] retain];

	//check all database subdirectories exist
	//create them if they dont along with their index

	[self _checkDatabaseSubdirectories];

	//unarchive all the database indexes and add them to the correct arrays

	systemIndex = [self _unarchiveIndexAtLocation: 
		[systemDir stringByAppendingPathComponent: @"SystemIndex"]];
	[systemIndex retain];
	optionsIndex = [self _unarchiveIndexAtLocation: 
		[optionsDir stringByAppendingPathComponent: @"OptionsIndex"]];
	[optionsIndex retain];
	dataSetIndex = [self _unarchiveIndexAtLocation: 
		[dataSetDir stringByAppendingPathComponent: @"DataSetIndex"]];
	[dataSetIndex retain];
	simulationIndex = [self _unarchiveIndexAtLocation: 
		[simulationDir stringByAppendingPathComponent: @"SimulationIndex"]];
	[simulationIndex retain];

	databaseIndexes = [NSMutableDictionary new];
	[databaseIndexes setObject: systemIndex forKey: @"ULSystem"];
	[databaseIndexes setObject: optionsIndex forKey: @"ULOptions"];
	[databaseIndexes setObject: dataSetIndex forKey: @"AdDataSet"];
	[databaseIndexes setObject: simulationIndex forKey: @"ULSimulation"];

	//check all the indexes are present - if they are missing reindex the
	//directories contents
	///FIXME: implement this

	//set an autosave timer

	userDefaults = [NSUserDefaults standardUserDefaults];
	
	if([userDefaults boolForKey: @"Autosave"])
	{
		autosaveTimer = [NSTimer scheduledTimerWithTimeInterval: 
						[userDefaults floatForKey: @"AutosaveInterval"]
					target: self
					selector: @selector(autosaveIndexes:)
					userInfo: nil
					repeats: YES];	
	}
}

- (id) init
{
	if(self = [super init])
	{
		[self _setWorkingEnvironment];
		if((userName = NSUserName()) == nil)
			userName = @"unknown";
		
		[userName retain];
		databaseName = [NSString stringWithFormat: @"%@@localhost", userName];
		[databaseName retain];
		contentInformation = [NSArray arrayWithObjects: 
					[NSDictionary dictionaryWithObjectsAndKeys:
						 @"ULSystem", @"ULObjectClassName",
						 @"Systems", @"ULObjectDisplayName", nil],
					[NSDictionary dictionaryWithObjectsAndKeys:
						 @"ULOptions", @"ULObjectClassName",
						 @"Options", @"ULObjectDisplayName", nil],
					[NSDictionary dictionaryWithObjectsAndKeys:
						 @"AdDataSet", @"ULObjectClassName",
						 @"DataSets", @"ULObjectDisplayName", nil],
					[NSDictionary dictionaryWithObjectsAndKeys:
						 @"ULSimulation", @"ULObjectClassName",
						 @"Simulations", @"ULObjectDisplayName", nil],
					nil];
		[contentInformation retain];			
	}

	return self;
}

- (void) dealloc
{
	[userName release];
	[contentInformation release];
	[databaseName release];
	[autosaveTimer invalidate];
	[databaseDir release];
	[systemDir release];
	[optionsDir release];
	[dataSetDir release];
	[simulationDir release];
	[databaseIndexes release];
	[systemIndex release];
	[optionsIndex release];
	[dataSetIndex release];
	[simulationIndex release];
}

- (NSString*) clientName
{
	return [[databaseName retain] autorelease];
}

- (NSArray*) contentTypeInformationForSchema: (NSString*) schema
{
	return [[contentInformation copy] autorelease];
}

- (void) addObject: (id) object toSchema: (NSString*) schema
{
	id index;

	if([object isKindOfClass: [AdModelObject class]])
	{
		//add database metadata to the object

		[[[object dataDictionary] objectForKey: @"General Data"]
			setObject: databaseName forKey: @"Database"];
		[[[object dataDictionary] objectForKey: @"General Data"]
			setObject: @"Local" forKey: @"Schema"];
		index = [databaseIndexes objectForKey: NSStringFromClass([object class])];
		[index addObject: object];
		[self _saveIndex: index ofClass: NSStringFromClass([object class])]; 
		[[NSNotificationCenter defaultCenter]
			postNotificationName: @"ULDatabaseBackendDidAddObjectNotification"
			object: object];
	}
	else
		[NSException raise: NSInvalidArgumentException
			format: @"Unable to add object %@ to database. Not a descendant of AdModelObject.",
			object];
}

- (BOOL) objectInDatabase: (id) object
{
	id index;
	
	if([object isKindOfClass: [AdModelObject class]])
	{
		index = [databaseIndexes objectForKey: NSStringFromClass([object class])];
		return [index objectInIndex: object];
	}
	else
		return NO;
}

- (void) updateMetadataForObject: (id) object inSchema: (NSString*) schema
{
	id index;

	index = [databaseIndexes objectForKey: NSStringFromClass([object class])];
	if(index != nil)
	{
		[index updateMetadataForObject: object];
		[self _saveIndex: index ofClass: NSStringFromClass([object class])]; 
		[[NSNotificationCenter defaultCenter]
			postNotificationName: @"ULDatabaseInterfaceDidUpdateMetadataNotification"
			object: object];
	}		
}

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

	index = [databaseIndexes objectForKey: NSStringFromClass([object class])];
	if(index != nil)
	{
		[index updateOutputReferencesForObject: object];
		[self _saveIndex: index ofClass: NSStringFromClass([object class])]; 
		[[NSNotificationCenter defaultCenter]
			postNotificationName: @"ULDatabaseInterfaceDidUpdateOutputReferencesNotification"
			object: object];
	}		
}

- (void) removeOutputReferenceToObjectWithID: (NSString*) identOne 
		fromObjectWithID: (NSString*) identTwo
		ofClass: (id) className 
		inSchema: (NSString*) schema;
{

	id index;

	index = [databaseIndexes objectForKey: className];
	[index removeOutputReferenceToObjectWithId: identOne
		fromObjectWithId: identTwo];
	[self _saveIndex: index ofClass: className]; 
	[[NSNotificationCenter defaultCenter]
		postNotificationName: @"ULDatabaseInterfaceDidUpdateOutputReferencesNotification"
		object: nil];
}

- (void) removeObjectOfClass: (id) className 
		withID: (NSString*) ident 
		fromSchema: (NSString*) schema
{
	id index;

	index = [databaseIndexes objectForKey: className];
	if(index != nil)
	{
		[index removeObjectWithId: ident];
		[self _saveIndex: index ofClass: className]; 
	}	
}

- (void) removeObjectsOfClass: (id) className 
		withIDs: (NSArray*) idents
		fromSchema: (NSString*) schema
{
	id index; 

	index = [databaseIndexes objectForKey: className];
	if(index != nil)
	{
		[index removeObjectsWithIds: idents];
		[self _saveIndex: index ofClass: className]; 
	}	
}

- (void) reindexAll
{
	NSWarnLog(@"Not implemented (%@)", NSStringFromSelector(_cmd));
}

- (id) unarchiveObjectWithID: (NSString*) ident 
	ofClass: (id) className
	fromSchema: (NSString*) schema
{
	id index, object;

	index = [databaseIndexes objectForKey: className];
	if(index == nil)
		return nil;

	object = [index unarchiveObjectWithId: ident]; 
	
	//add volatile metadata attribute "clientName" so we can identify later
	//which client the object belongs to
	[object setValue: [[databaseName copy] autorelease] 
		forVolatileMetadataKey: @"DatabaseClient"];

	return object;
}

- (void) saveIndexes
{
	NSEnumerator* indexEnum;
	id indexClass;

	indexEnum = [databaseIndexes keyEnumerator];
	while(indexClass = [indexEnum nextObject])
		[self _saveIndex: [databaseIndexes objectForKey: indexClass]
			ofClass: indexClass];
}

- (void) saveDatabase
{
	[self saveIndexes];
}

- (void) autosaveIndexes: (id) info
{
	[self saveIndexes];
} 

- (NSArray*) availableObjectsOfClass: (id) className
		inSchema: (NSString*) schema;
{
	id index;

	index = [databaseIndexes objectForKey: className];
	return [index availableObjects];
}

- (NSDictionary*) metadataForObjectWithID: (NSString*) ident 
			ofClass: (id) className
			inSchema: (NSString*) schema
{
	id index;

	index = [databaseIndexes objectForKey: className];
	return [index metadataForObjectWithID: ident];
}

- (NSArray*) outputReferencesForObjectWithID: (NSString*) ident 
		ofClass: (id) className 
		inSchema: (NSString*) schema
{
	id index;

	index = [databaseIndexes objectForKey: className];
	return [index outputReferencesForObjectWithID: ident];
}

- (NSArray*) inputReferencesForObjectWithID: (NSString*) ident 
		ofClass: (id) className
		inSchema: (NSString*) schema
{
	id index;

	index = [databaseIndexes objectForKey: className];
	return [index inputReferencesForObjectWithID: ident];
}

- (id) createStorageForSimulation: (id) object
{
	NSString* path;

	path = [simulationDir stringByAppendingPathComponent:
			[NSString stringWithFormat: @"%@_Data",
				[object identification]]];

	return [[[ULFileSystemSimulationStorage alloc]
			initStorageForSimulationAtPath: path] autorelease];
}

- (NSArray*) schemaInformation
{
	return [NSArray arrayWithObject: [NSDictionary dictionaryWithObjectsAndKeys:
		@"Local", @"ULSchemaName",
		@"Usage", @"ULSchemaPrivileges", 
		userName, @"ULDatabaseUserName",
		[NSNumber numberWithBool: YES], @"ULSchemaOwner",
		databaseName, @"ULDatabaseClientName", nil]];
}

- (NSString*) simulationDir
{
	return [[simulationDir retain] autorelease];
}

- (NSString*) templateDir
{
	return [[optionsDir retain] autorelease];
}

- (NSString*) systemDir
{
	return [[systemDir retain] autorelease];
}

- (NSString*) dataSetDir
{
	return [[dataSetDir retain] autorelease];
}

@end
