/*!
  @header BDBDatabase
  @abstract Contains a class representing a database
 
  @availability OS X, GNUstep
  @copyright (C) 2004, 2005, 2006 Oliver Langer
 
  Author: Oliver Langer

  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.12.2005 ola     initial version
  22.08.2006 ola     license changed
  -------------------------------------------------------------------------
  </pre>
******************************************************************************/

#include <string.h>
#include <BDB/BDB.h>
#include "BDBExceptions.h"
#include <Encore/Encore.h>
#include "BDBErrorHandling.h"
#include <BDB/BDBDatabase.h>
#include <BDB/BDBCursor.h>

static ECLogger *_log;

@implementation BDBDatabase

+ (BDBDatabase *) initWithFilename: (NSString *) fileName 
  databaseName: (NSString *) aDatabaseName 
  databaseConfig: (BDBDatabaseConfig *) aDbConfig {
  BDBDatabase *toReturn = nil;
  int result;
  
  toReturn = [ [ BDBDatabase alloc ] init ];

  toReturn->dbConfig = [ aDbConfig retain ];
  toReturn->databaseName = [ aDatabaseName retain ];
  toReturn->db = [aDbConfig db];
  
  result = toReturn->db->open( toReturn->db, NULL, [ fileName cString ], 
    (aDatabaseName != nil ) ? [ aDatabaseName cString ] : NULL,
	[ toReturn->dbConfig databaseType ], [ toReturn->dbConfig openFlags ], 0 );

  return toReturn;
}


+ (void) initialize {
  _log = [[ECLogging loggerForContext: LOGCONTEXT_BDB] retain];
}


- init {
  self = [ super init ];
  self->db = NULL;
  self->databaseName = nil;
  return self;
}


- (void) dealloc {
  if( NULL != db ) {
    if( [_log isDebugEnabled] ) {
      NSString *s;

      if( nil != self->databaseName ) {
        s = self->databaseName;
      } else {
        s= @"NOT SPECIFIED";
      }

      [_log debug: @"Closing database name=%@", s ];
    }
    db->close( db, 0 );
	db = NULL;
  }
  
  [ super dealloc ];
}


- (BDBOperationStatus) appendEntryWithTransaction: 
  (BDBTransaction *) transaction 
  entryToAppend: (BDBDatabaseEntryData *) entryToAppend
  resultingRecordNr: (BDBDatabaseRecordNumber *)  recordNr {
  BDBOperationStatus status = BDB_STATUS_UNKNOWN;
  NSData *dataToAppend;
  
  NSAssert( nil == transaction, @"Transaction not supported at present!" );
  
  if( (BDB_QUEUE != [dbConfig databaseType]) 
    && (BDB_RECNO != [dbConfig databaseType]) ) {
    [[[ECIllegalOperationException alloc] 
      initWithOperationInformation: @"BDBDatabase::appendEntryWithTransaction:"\
        "This operation is only defined for databases of type BDB_QUEUE and"\
        " BDB_RECNO"] raise];
  }
  
  EC_AUTORELEASEPOOL_BEGIN
  DBT dbKey, dbValue;
  
  dataToAppend = [entryToAppend data];
  if( nil == dataToAppend ) {
    [[[ECIllegalArgumentException alloc] 
      initWithArgumentInfo: @"BDBDatabase::appendEntryWithTransaction:"\
        "Given database entry contains no data!"] raise];
  }
  
  memset( &dbKey, 0, sizeof( DBT ) );
  memset( &dbValue, 0, sizeof( DBT ) );
  dbValue.data = (void *) [dataToAppend bytes];
  dbValue.size = [dataToAppend length];
  
  status = db->put( db, NULL, &dbKey, &dbValue, DB_APPEND );
  if( BDB_STATUS_SUCCESS == status ) {
    if( nil != recordNr ) {
      NSAssert( dbKey.size == sizeof( u_int32_t ), 
        @"BDBDatabase::appendEntryWithTransaction: Returned key is NOT of type"\
        " u_int32_t." );
      [recordNr setEntryNumber: (*((u_int32_t *) dbKey.data))];
    }
  } else {
    if( [_log isDebugEnabled] ) {
      [_log debug:@"BDBDatabase::appendEntryWithTransaction: "\
      "db->put returned with error code %u.", status ];
    }
    
    switch( status ) {
      case DB_LOCK_DEADLOCK: 
        [[[BDBDeadLockException alloc] initWithErrorCode: (int) status] raise];
      case DB_LOCK_NOTGRANTED:
        [[[BDBLockNotGrantedException alloc] 
          initWithErrorCode: (int) status] raise];
      case EACCES:
        [[[ECIllegalOperationException alloc] 
          initWithOperationInformation: 
            @"BDBDatabase::appendEntryWithTransaction: Trying to modify a "\
            "read-only database!"] raise];
      case DB_REP_HANDLE_DEAD:
        [[[BDBInvalidDatabaseHandle alloc] 
          initWithErrorCode: (int) status] raise];
      case EINVAL:
        [[[ECIllegalArgumentException alloc] 
          initWithArgumentInfo: 
            @"BDBDatabase::appendEntryWithTransaction: Unable to append the "\
            "specified database entry. Check the size of the record. Possibly"\
            " an attempt was made to append data to a secondary index." ] 
          raise];
      case ENOSPC:
        [[[BDBBtreeMaximumDepthReachedException alloc] 
          initWithErrorCode: (int) status] raise];
      default: /* ok */ break;
    }
  }
  
  EC_AUTORELEASEPOOL_END
  
  return status;
}


- close {
  if( NULL != db ) {
    db->sync( db, 0 );
    db->close( db, 0 );
	db = NULL;
	[ databaseName release ];
	databaseName = nil;
  }
  
  return self;
}


- (BDBOperationStatus) deleteEntryWithTransaction:( BDBTransaction *) transaction
  key: (BDBDatabaseEntryData *) key {
  BDBOperationStatus status = BDB_STATUS_UNKNOWN;
  DBT dbKey;
  
  NSAssert( nil == transaction, @"Transaction not supported at present!" );
  
  memset( &dbKey, 0, sizeof( dbKey ) );
  dbKey.data = (void *) [[key data] bytes];
  dbKey.size = [[key data] length];
  
  status = db->del( db, NULL, &dbKey, 0 );
  
  BDB_ERROR_CODE_EVAL_BEGIN(status);
  BDB_ERROR_CODE_EVAL_END;
  
  return status;
}


- (BDBOperationStatus) getEntryWithTransaction: (BDBTransaction *) transaction 
  key: (BDBDatabaseEntryData *) key data: (BDBDatabaseEntryData *) data
  flags: (u_int32_t) flags {
NSAutoreleasePool *pool = [ [ NSAutoreleasePool alloc ] init ];
  NSData *valueData;
  DBT dbKey, dbValue;
  int dbResult = BDB_STATUS_UNKNOWN;
  
  // todo 
  NSAssert( nil == transaction, @"Transaction not supported at present!" );
  if( nil == key || nil == data ) {
    [[[ECIllegalArgumentException alloc ] 
      initWithArgumentInfo: @"key or data equals nil"] raise];
  }
  
  memset( &dbKey, 0, sizeof( dbKey ) );
  memset( &dbValue, 0, sizeof( dbValue ) );

  dbKey.data = (void *) [ [ key data] bytes ];
  dbKey.size = [ [ key data ] length ];

  dbValue.flags = DB_DBT_MALLOC;
  
  dbResult = db->get(db, NULL, &dbKey, &dbValue, flags);

  BDB_ERROR_CODE_EVAL_BEGIN(dbResult);
    BDB_ERROR_CODE_EVAL_ON_THROW_BEGIN
	  [ pool release ];
	  if( NULL != dbValue.data ) {
        free( dbValue.data );
      }
	BDB_ERROR_CODE_EVAL_ON_THROW_END
  BDB_ERROR_CODE_EVAL_END;

  if( BDB_STATUS_SUCCESS == dbResult ) {
    /* at present we do not support user buffers, so :*/
    valueData = [ [ [ NSData alloc ] initWithBytes:dbValue.data 
      length:dbValue.size ]  autorelease ] ;
	[ data setData: valueData ];
  }
  
  if( NULL != dbValue.data ) {
    free( dbValue.data );
  }
  
  [ pool release ];

  return dbResult;
}


- (BDBOperationStatus) getEntryWithTransaction: (BDBTransaction *) transaction 
  key: (BDBDatabaseEntryData *) key data: (BDBDatabaseEntryData *) data {

  return [self getEntryWithTransaction: transaction key:key 
    data: data flags: 0];
}


- (BDBOperationStatus) getEntryWithTransaction: (BDBTransaction *) transaction
  recordNumber: (BDBDatabaseRecordNumber *) recNo 
  data: (BDBDatabaseEntryData *) data {
  u_int32_t flags;
  
  if( BDB_BTREE == [self->dbConfig databaseType] ) {
    flags = DB_SET_RECNO;
  } else {
    flags = 0;
  }

  return [ self getEntryWithTransaction: transaction key:recNo data: data
    flags: flags ];
}


- (BDBCursor *) openCursor: (BDBTransaction *) transaction 
  cursorConfig: (BDBCursorConfig *) config {
  DBC *cursor;
  BDBOperationStatus result;
  BDBCursor *toReturn = nil;
  
  // todo 
  NSAssert( nil == transaction, @"Transaction not supported at present!" );
  
  result = db->cursor( db, NULL, &cursor, [config flags] );
  BDB_ERROR_CODE_EVAL_BEGIN(result);
    BDB_ERROR_CODE_EVAL_ON_THROW_BEGIN
	BDB_ERROR_CODE_EVAL_ON_THROW_END
  BDB_ERROR_CODE_EVAL_END;
  
  NSAssert( BDB_STATUS_SUCCESS == result, @"Got unknown error code" );
  toReturn = [[BDBCursor alloc] initWithCursor: cursor config: config ];
  
  return [toReturn autorelease];
}


- (BDBOperationStatus) putEntryWithTransaction: (BDBTransaction *) transaction 
  key: (BDBDatabaseEntryData *) aKey value: (BDBDatabaseEntryData *) value {
  NSData *keyData, *valueData;
  BDBOperationStatus toReturn = BDB_STATUS_UNKNOWN;
  
  // todo
  NSAssert( nil == transaction, @"Transaction not supported at present!" );
  
  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  
  keyData = [ aKey data ];
  valueData = [ value data ];
  
  toReturn = [ self putDataWithTransaction: transaction 
    key: keyData value:valueData ];
  [ pool release ];
  
  return toReturn;
}


- (BDBOperationStatus) putDataWithTransaction: (BDBTransaction *) transaction
  key: (NSData *) key value: (NSData *) value {
  DBT dbKey, dbValue;
  BDBOperationStatus result = BDB_STATUS_UNKNOWN;

  // todo
  NSAssert( nil == transaction, @"Transaction not supported at present!" );
  
  memset( &dbKey, 0, sizeof( DBT ) );
  memset( &dbValue, 0, sizeof( DBT ) );
	
  dbKey.data = (void *) ([ key bytes ]);
  dbKey.size = [ key length ];
  
  dbValue.data = (void *) ([ value bytes ]);
  dbValue.size = [ value length ];

  result = db->put( db, NULL, &dbKey, &dbValue, 0 );
  BDB_ERROR_CODE_EVAL_BEGIN(result);
  BDB_ERROR_CODE_EVAL_END;
  
  if( BDB_STATUS_SUCCESS != result ) {
    [[[BDBException alloc ] initWithErrorCode: result] raise];
  }
  
  return result;
}
@end
