/*!
  @header FTDictionaryServiceForGraphImpl
  @abstract Module of FT
 
  @availability OS X, GNUstep
  @copyright 2004, 2005, 2006 Free Software Foundation, Inc.
  
  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 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
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA

  <pre>
  -------------------------------------------------------------------------
  Modification history

  16.07.06 ola     initial version
  23.08.06 ola     license changed
  -------------------------------------------------------------------------
  </pre>
*/

#include "FTDictionaryServiceForGraphImpl.h"
#include <FT/FTLogging.h>
#include <FT/FTDictionaryServiceLoader.h>
#include <FT/FTDictionaryServiceForNodeImpl.h>


@implementation FTDictionaryServiceForGraphImpl

- initForGraph: (id <FTGraph>) aGraph
  serviceLoader: (FTDictionaryServiceLoader *) aServiceLoader {
  self = [super init];
  
  self->database = nil;
  self->databaseIsOpen = NO;
  self->serviceLoader = [aServiceLoader retain];
  self->writeLock = [[NSLock alloc] init];
  
  if( ![aGraph isKindOfClass: [FTGraphImpl class]] ) {
    [[FTLogging coreLog]
      error: @"FTDictionaryServiceForGraphImpl::initForGraph: Given graph is "\
        "NOT kind of class FTGraphImpl... Graph: %@", aGraph];
        
    [[[ECIllegalStateException alloc]
      initWithIllegalStateInfo:
        [NSString stringWithFormat: 
          @"FTDictionaryServiceForGraphImpl::initForGraph: Given graph is "\
          "NOT kind of class FTGraphImpl... Graph: %@", aGraph]]
       raise];
  }
  
  [self openDatabaseForGraph: (FTGraphImpl *) aGraph
    usingDatabaseName: [aServiceLoader databaseNameScheme]];
  
  return self;
} 


- (void) dealloc {
  [self close];
  [self->serviceLoader release];
  [self->writeLock release];
  
  [super dealloc];
}


- (void) close {
  [self->writeLock lock];

  NS_DURING
    if( YES == self->databaseIsOpen ) {
      if( nil != self->database ) {
        [self->database close];
        [self->database release];
        self->database = nil;
      }
    
      self->databaseIsOpen = NO;
    } else {
      [self->database release];
    }
  NS_HANDLER
    [self->writeLock unlock];
    [localException raise];
  NS_ENDHANDLER

  [self->writeLock unlock];
}


- (BOOL) isOpen {
  return self->databaseIsOpen;
}


- addDatabaseEntry: (BDBDatabaseEntry *) anEntry 
  forDatabaseKey: (BDBDatabaseEntry *) aKey {
  BDBOperationStatus status = BDB_STATUS_UNKNOWN;
  
  NS_DURING
    status = [self->database 
      putEntryWithTransaction: nil
      key: aKey
      value: anEntry];
      
    if( BDB_STATUS_SUCCESS != status ) {
      [[[FTDatabaseUpdateException alloc]
        initWithOperationStatus: status
        operationInformation: @"FTDictionaryServiceForGraphImpl::"\
          "addDatabaseEntry: Error while updating the BDB database"]
        raise];
    }
  NS_HANDLER
    FTDatabaseUpdateException *ex;
    
    [[FTLogging coreLog]
      error: @"FTDictionaryServiceForGraphImpl::addDatabaseEntry: Error while "\
       "updating the BDB database"];
       
     if( ![localException isKindOfClass: [FTDatabaseUpdateException class]] ) {
       ex = [[FTDatabaseUpdateException alloc]
        initWithOperationStatus: status
        operationInformation: @"FTDictionaryServiceForGraphImpl::"\
          "addDatabaseEntry: Error while updating the BDB database"];
       [ex setCause: localException];
     } else {
       ex = (FTDatabaseUpdateException *) localException;
     }
     
     [ex raise];
  NS_ENDHANDLER
    
  return self;
}


- addObject: (id <NSCoding>) anObject forKey: (id <NSCoding>) aKey
  forNode: (id <FTNode>) aNode {
  BDBDatabaseEntry *dbKey, *dbValue;
  _FTDictionaryServiceKeysOfNode *keysOfNode = nil;
  
  if( [[FTLogging coreLog] isTraceEnabled] ) {
    [[FTLogging coreLog]
      trace: @"FTDictionaryServiceForGraphImpl::addObject: forKey=\"%@\""\
        " forNodeId=\"%@\"", aKey, [aNode nodeId]];
  }

  [self->writeLock lock];
  
  NS_DURING
    /* add the data: */
    dbKey = [self
      createDatabaseEntryKeyForNodeId: [aNode nodeId]
      forKey: aKey];
  
    dbValue = [[[BDBDatabaseEntry alloc] initWithObject: anObject] autorelease];
  
    [self addDatabaseEntry: dbValue forDatabaseKey: dbKey];
    
    /* now add the key itself to the dictionary as well: */
    keysOfNode = [[_FTDictionaryServiceKeysOfNode alloc]
      initForNode: aNode
      dictionaryServiceForGraph: self];
      
    [keysOfNode addKey: aKey];
  NS_HANDLER
    [self->writeLock unlock];
    [keysOfNode release];
    [localException raise];
  NS_ENDHANDLER
  
  [keysOfNode release];
  [self->writeLock unlock];
  
  return self;
}


- (id <ECIterator>) allKeysOfNode: (id <FTNode>) aNode {
  _FTDictionaryServiceKeysOfNode *dictOfNode 
    = [[_FTDictionaryServiceKeysOfNode alloc]
        initForNode: aNode
        dictionaryServiceForGraph: self];
        
  return [dictOfNode allKeys];
}


- (BDBDatabaseEntry *) createDatabaseEntryKeyForNodeId: (id <FTId>) aNodeId
  forKey: (id <NSCoding>) aKey {
  NSMutableData *keyData = [[[NSMutableData alloc] init] autorelease];
  NSKeyedArchiver *archiver = [[[NSKeyedArchiver alloc] 
    initForWritingWithMutableData: keyData]
    autorelease];
    
  [archiver encodeObject: aNodeId forKey: @"nodeId"];
  [archiver encodeObject: aKey forKey: @"key"];
  [archiver finishEncoding];
  
  return [[[BDBDatabaseEntry alloc]
    initWithData: keyData]
    autorelease];
}


- (BDBDatabaseEntry *) databaseEntryForKey: (BDBDatabaseEntry *) aDBKey {
  BDBDatabaseEntry *toReturn = [[[BDBDatabaseEntry alloc] init] autorelease];
  BDBOperationStatus opStatus = BDB_STATUS_UNKNOWN;
  
  NS_DURING
    opStatus = [self->database 
      getEntryWithTransaction: nil
      key: aDBKey
      data:toReturn];
  NS_HANDLER
    [[FTLogging coreLog] 
      error: @"FTDictionaryServiceForGraphImpl::databaseEntryForKey: "\
        "Operation failed due to exception: \"%@\". Key=%@",
        localException, aDBKey];
        
    [localException raise];
  NS_ENDHANDLER
  
  if( BDB_STATUS_SUCCESS != opStatus ) {
    if( BDB_STATUS_NOTFOUND == opStatus ) {
      /* entry not found -> return nil */
      toReturn = nil;
    } else {
      [[[FTInternalDatamanagementException alloc]
        initWithOperationStatus: opStatus]
        raise];
    }
  }
  
  return toReturn;
}


- objectForKey: (id <NSCoding>) aKey ofNode: (id <FTNode>) aNode {
  BDBDatabaseEntry *dbKey;
  BDBDatabaseEntry *dbValue;
  id toReturn;
  
  if( [[FTLogging coreLog] isTraceEnabled] ) {
    [[FTLogging coreLog] 
      trace: @"FTDictionaryServiceForGraphImpl::objectForKey"];
  }
  
  dbKey = [self 
    createDatabaseEntryKeyForNodeId: [aNode nodeId]
    forKey: aKey];
    
  dbValue = [self databaseEntryForKey: dbKey];
  
  if( nil != dbValue ) {
    toReturn = [dbValue object];
  }
  
  return toReturn;
}


- openDatabaseForGraph: (FTGraphImpl *) aGraph 
   usingDatabaseName: (NSString *) aDatabaseName {
  NSString *databaseName;
  BDBDatabaseConfig * dbConfig;
  
  if( YES == self->databaseIsOpen ) {
    if( nil != self->database ) {
      [self->database close];
      [self->database release];
    }
  }
  
  databaseName = [NSString
    stringWithFormat: @"%@/%@", 
      [aGraph graphDatabaseDirectory],
      aDatabaseName];
  
  dbConfig = [[[BDBDatabaseConfig alloc] init]
    autorelease];
  [dbConfig setDatabaseType: BDB_BTREE];
  [dbConfig setBTreeRecordNumbering: NO];
  [dbConfig setAllowDuplicates: NO];
   
  /*
   * Check wether a file databaseName already exists. If so, then simply 
   * use it as dictionary service db; otherwise create one:
   */
  if( ![[NSFileManager defaultManager]
    fileExistsAtPath: databaseName] ) {
    [dbConfig setAllowCreate: YES];
  }
   
  self->database = [BDBDatabase 
    initWithFilename: databaseName
    databaseName: nil 
    databaseConfig: dbConfig];
    
  if( nil != self->database ) {
    self->databaseIsOpen = YES;
  } 
  
  return self;
}


- removeDatabaseEntryForKey: (BDBDatabaseEntry *) key {
  [self->database deleteEntryWithTransaction: nil key: key];
  
  return self;
}


- removeObjectForKey: (id <NSCoding>) aKey ofNode: (id <FTNode>) aNode {
  BDBDatabaseEntry *dbKey;
  _FTDictionaryServiceKeysOfNode *servicesOfNode;
    
  if( [[FTLogging coreLog] isTraceEnabled] ) {
    [[FTLogging coreLog]
      trace: @"FTDictionaryServiceForGraphImpl::removeObjectForKey" ];
  }
  
  dbKey = [self 
    createDatabaseEntryKeyForNodeId: [aNode nodeId]
    forKey: aKey];
  
  [self removeDatabaseEntryForKey: dbKey];
  
  servicesOfNode = [[[_FTDictionaryServiceKeysOfNode alloc]
    initForNode: aNode
    dictionaryServiceForGraph: self] autorelease];
    
  [servicesOfNode removeKey: aKey];
  
  return self;
}


- (id <FTDictionaryService>) serviceForNode: (id <FTNode>) aNode {
  return [[[FTDictionaryServiceForNodeImpl alloc] 
    initForDictionaryServiceForGraph: self
    forNode: aNode] autorelease];
}


- (id <FTServiceLoader>) serviceLoader {
  return self->serviceLoader;
}
@end


/** 
 * Used as key in order to store keys per node
 */
#define _Key4NodeKey @"__nodeKey"

@implementation _FTDictionaryServiceKeysOfNode
 
- initForNode: (id <FTNode>) aNode 
  dictionaryServiceForGraph: (FTDictionaryServiceForGraphImpl *) aDictService {
  self = [super init];
  
  self->node = [aNode retain];
  self->dictionaryForGraph = [aDictService retain];
 
  return self;
}


- (void) dealloc {
  [self-> node release];
  [self->dictionaryForGraph release];
  
  [super dealloc];
}


- (id <ECIterator>) allKeys {
  return [[[ECEnumeratorIterator alloc]
    initWithEnumerator: [[self fetchKeys] objectEnumerator]]
    autorelease];
}


- addKey: (id <NSCoding>) aKey {
  NSMutableSet *allKeys = [self fetchKeys];
  [allKeys addObject: aKey];
  
  [self storeKeys: allKeys];
  
  return self;
}


- (BDBDatabaseEntry *) dbKeyForAllkeys {
  return [self->dictionaryForGraph 
    createDatabaseEntryKeyForNodeId: [self->node nodeId]
    forKey: _Key4NodeKey];
}


- (NSMutableSet *) fetchKeys {
  BDBDatabaseEntry *key;
  BDBDatabaseEntry *entry;
  NSMutableSet *toReturn;
  
  key = [self dbKeyForAllkeys];
    
  entry = [self->dictionaryForGraph
    databaseEntryForKey: key];
  
  if( nil == entry ) {
    toReturn = [[[NSMutableSet alloc] init] autorelease];
  } else {
    toReturn = [entry object];
  }
  
  return toReturn;
}


- removeKey: (id <NSCoding>) aKey {
  NSMutableSet *allKeys = [self fetchKeys];

  if( [allKeys containsObject: aKey] ) {
    [allKeys removeObject: aKey];
    [self storeKeys: allKeys];
  }
  
  return self;
}


- storeKeys: (NSMutableSet *) allKeys {
  BDBDatabaseEntry *key = [self dbKeyForAllkeys];
  BDBDatabaseEntry *value;
  
  [self->dictionaryForGraph removeDatabaseEntryForKey: key];
  value = [[[BDBDatabaseEntry alloc] initWithObject: allKeys] autorelease];
  [self->dictionaryForGraph addDatabaseEntry: value forDatabaseKey: key];
  
  return self;
}
@end

