/*!
  @header FTDefaultObjectToIdMapper
  @abstract Maps objects (usually strings) to identifier.
 
  @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

  09.02.2005 ola     initial version
  23.08.2006 ola     license changed
  -------------------------------------------------------------------------
  </pre>
*/

#include <Encore/Encore.h>
#include <FT/FTExceptions.h>
#include <FT/FTDefaultObjectToIdMapper.h>
#include <FT/FTIdImpl.h>
#include <FT/FTServerImpl.h>
#include <FT/FTLogging.h>

@implementation FTDefaultObjectToIdMapper

- initWithDatabaseName: (NSString *) aDatabaseName 
  forServer: (FTServerImpl *) aServer {
  self = [super init];
  self->database = nil;
  self->databaseName = [aDatabaseName retain];
  self->insertLock = [[NSLock alloc] init];
  self->server = [aServer retain];

  return self;
}


- (void) dealloc {
  [self->server release];
  if( nil != self->database ) {
    [self unmountDatabase];
  }
  [self->insertLock release];
  [self->databaseName release];
  
  [super dealloc];
}


- (BDBDatabaseConfig *) dbConfig {
  BDBDatabaseConfig * dbConfig = [[BDBDatabaseConfig alloc] init];

  [dbConfig setDatabaseType: BDB_BTREE];
  [dbConfig setBTreeRecordNumbering: YES];
  [dbConfig setAllowDuplicates: NO];
  
  return dbConfig;
}


- (id <FTId>) lookupObject: (id) toLookup {
  FTIdImpl *foundObject = nil;
  BDBDatabaseEntry *entryKey;
  BDBDatabaseEntry *entryValue;
  
  if( nil == self->database ) {
    [[[ECIllegalStateException alloc] 
	  initWithIllegalStateInfo: @"FTDefaultObjectToIdMapper::lookupObject: "\
	    "Database not mounted!" ] raise];
  }
  
  EC_AUTORELEASEPOOL_BEGIN
  
  entryKey = [[[BDBDatabaseEntry alloc] initWithObject: toLookup] 
    autorelease ];
  entryValue = [[[BDBDatabaseEntry alloc] init] autorelease];
  
  BDBOperationStatus operationStatus;
  operationStatus = BDB_STATUS_UNKNOWN;
  
  NS_DURING
    operationStatus = [database getEntryWithTransaction: nil
	  key: entryKey data: entryValue ];
	if( BDB_STATUS_SUCCESS == operationStatus ) {
	  foundObject = (FTIdImpl *) ([[entryValue object] retain]);
	}
  NS_HANDLER
    if( [localException isKindOfClass: [BDBException class]] ) {
      BDBException *bdbException = (BDBException *) localException;
      FTInternalDatamanagementException *ex = 
        [[FTInternalDatamanagementException alloc ] 
	      initWithBDBException: bdbException ];

      [ex setCause: localException];
      [ex raise];
    } else {
      FTUnknownException *ex = [[FTUnknownException alloc]
        initWithContextInfo: @"While fetching a BDB entry"
	    exception: localException ];

      [ex setCause: localException];
	  [ex raise];
    }
  NS_ENDHANDLER

  if( BDB_STATUS_SUCCESS != operationStatus ) {
    if( BDB_STATUS_NOTFOUND != operationStatus ) {
      // we're having an error	  
      [[[FTInternalDatamanagementException alloc]
	    initWithOperationStatus: operationStatus] raise];
	}
  }
  
  EC_AUTORELEASEPOOL_END
  
  return foundObject;
}


- (id <FTId>) mapObject: (NSString *) strObject {
  BDBDatabaseEntry *entryKey;
  BDBDatabaseEntry *entryValue;
  FTIdImpl *toReturn = nil;
  
  if( nil == self->database ) {
    [[[ECIllegalStateException alloc] 
	  initWithIllegalStateInfo: @"FTDefaultObjectToIdMapper::lookupObject: "\
	    "Database not mounted!" ] raise];
  }
  
  EC_AUTORELEASEPOOL_BEGIN
    if( nil == strObject ) {
      [[[ECIllegalArgumentException alloc] 
	    initWithArgumentInfo: @"Given object equals nil"] raise];
    }
  
  if( [strObject length] < 1 ) {
    [[[ECIllegalArgumentException alloc]
	  initWithArgumentInfo: @"Given string contains no characters"] raise];
  }
  
  NS_DURING
    [insertLock lock];
    toReturn = (FTIdImpl *) [self lookupObject: strObject];
  NS_HANDLER
    /**
	 * release lock since we got an exception
	 */
    [insertLock unlock];
	[localException raise];
  NS_ENDHANDLER
  
  if( nil == toReturn ) {
    /* entry seems not to exist in db -> create new one.
	 * At present we simply use the key as value. This may change in future...
	 */
    entryKey = [[[BDBDatabaseEntry alloc] initWithObject: strObject] 
	  autorelease ];
		
	toReturn = [[FTIdImpl alloc] initWithString: strObject];
	
	entryValue = [[[BDBDatabaseEntry alloc] initWithObject: toReturn] 
	  autorelease ];
	  
	NS_DURING
	  BDBOperationStatus operationStatus;
	  
	  operationStatus = [database putEntryWithTransaction: nil
	    key: entryKey value: entryValue ];
		
	  if( BDB_STATUS_SUCCESS != operationStatus ) {
	    [[[FTInternalDatamanagementException alloc]
	      initWithOperationStatus: operationStatus] raise];
	  }
	  
	NS_HANDLER
	  [insertLock unlock];
	  [localException raise];
	NS_ENDHANDLER
  }
 
  [insertLock unlock];
  EC_AUTORELEASEPOOL_END
  
  return [toReturn autorelease];
}


- mountDatabase {
  BDBDatabaseConfig *dbConfig;
  EC_AUTORELEASEPOOL_BEGIN
  
  if( [[FTLogging coreLog] isDebugEnabled] ) {    
      [[FTLogging coreLog]
        debug: @"FTDefaultObjectToIdMapper::mountDatabase: "\
          "Opening database %@...", self->databaseName ];
  }
  
  if( ![[NSFileManager defaultManager] fileExistsAtPath: self->databaseName] ) {
    NSMutableString *info = [[NSMutableString alloc]
	  initWithFormat: @"FTDefaultObjectToIdMapper::setupDatabase: "\
	    "Database named %@ does not exist", self->databaseName];
    [[[ECIllegalStateException alloc] 
	  initWithIllegalStateInfo: info] raise];
  }
  
  dbConfig = [[self dbConfig] autorelease];
  self->database = [BDBDatabase initWithFilename: self->databaseName
    databaseName: nil databaseConfig: dbConfig];
  if( [[FTLogging coreLog] isDebugEnabled] ) {    
    [[FTLogging coreLog]
      debug: @"FTDefaultObjectToIdMapper::mountDatabase: DONE" ];
  }
  
  EC_AUTORELEASEPOOL_END
  return self;
}


- setupDatabase {
  BDBDatabaseConfig *dbConfig;
  
  EC_AUTORELEASEPOOL_BEGIN
  
  /**
   * does the file already exist?
   */
  if( [[NSFileManager defaultManager] fileExistsAtPath: self->databaseName] ) {
    NSMutableString *info = [[NSMutableString alloc]
	  initWithFormat: @"FTDefaultObjectToIdMapper::setupDatabase: "\
	    "Database named %@ already exists", self->databaseName];
    [[[ECIllegalStateException alloc] 
	  initWithIllegalStateInfo: info] raise];
  }
  
  if( [[FTLogging coreLog] isDebugEnabled] ) {    
    [[FTLogging coreLog]
      debug: @"FTDefaultObjectToIdMapper::setupDatabase: "\
        "Creating database %@...", self->databaseName ];
  }
	
  dbConfig = [[self dbConfig] autorelease];
  [dbConfig setAllowCreate: YES];
  
  self->database = [BDBDatabase initWithFilename: self->databaseName
    databaseName: nil databaseConfig: dbConfig];

  if( [[FTLogging coreLog] isDebugEnabled] ) {    
    [[FTLogging coreLog]
      debug: @"FTDefaultObjectToIdMapper::setupDatabase: DONE" ];
  }
  
  EC_AUTORELEASEPOOL_END
  return self;
}


- unmountDatabase {
  if( nil != self->database ) {
    NS_DURING
      [self->database close];
	NS_HANDLER
      [self->database release];
      self->database = nil;
	NS_ENDHANDLER
  }

  return self;
}
@end
